1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * 
  4  * Zimbra Collaboration Suite Web Client
  5  * Copyright (C) 2012 VMware, Inc.
  6  * 
  7  * The contents of this file are subject to the Zimbra Public License
  8  * Version 1.3 ("License"); you may not use this file except in
  9  * compliance with the License.  You may obtain a copy of the License at
 10  * http://www.zimbra.com/license.
 11  * 
 12  * Software distributed under the License is distributed on an "AS IS"
 13  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
 14  * 
 15  * ***** END LICENSE BLOCK *****
 16  */
 17 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
 18 (function(win) {
 19 	var whiteSpaceRe = /^\s*|\s*$/g,
 20 		undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
 21 
 22 	var tinymce = {
 23 		majorVersion : '3',
 24 
 25 		minorVersion : '5.4.1',
 26 
 27 		releaseDate : '2012-06-24',
 28 
 29 		_init : function() {
 30 			var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
 31 
 32 			t.isOpera = win.opera && opera.buildNumber;
 33 
 34 			t.isWebKit = /WebKit/.test(ua);
 35 
 36 			t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
 37 
 38 			t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
 39 
 40 			t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
 41 
 42 			t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
 43 
 44 			t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
 45 
 46 			t.isGecko = !t.isWebKit && /Gecko/.test(ua);
 47 
 48 			t.isMac = ua.indexOf('Mac') != -1;
 49 
 50 			t.isAir = /adobeair/i.test(ua);
 51 
 52 			t.isIDevice = /(iPad|iPhone)/.test(ua);
 53 			
 54 			t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
 55 
 56 			// TinyMCE .NET webcontrol might be setting the values for TinyMCE
 57 			if (win.tinyMCEPreInit) {
 58 				t.suffix = tinyMCEPreInit.suffix;
 59 				t.baseURL = tinyMCEPreInit.base;
 60 				t.query = tinyMCEPreInit.query;
 61 				return;
 62 			}
 63 
 64 			// Get suffix and base
 65 			t.suffix = '';
 66 
 67 			// If base element found, add that infront of baseURL
 68 			nl = d.getElementsByTagName('base');
 69 			for (i=0; i<nl.length; i++) {
 70 				v = nl[i].href;
 71 				if (v) {
 72 					// Host only value like http://site.com or http://site.com:8008
 73 					if (/^https?:\/\/[^\/]+$/.test(v))
 74 						v += '/';
 75 
 76 					base = v ? v.match(/.*\//)[0] : ''; // Get only directory
 77 				}
 78 			}
 79 
 80 			function getBase(n) {
 81 				if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
 82 					if (/_(src|dev)\.js/g.test(n.src))
 83 						t.suffix = '_src';
 84 
 85 					if ((p = n.src.indexOf('?')) != -1)
 86 						t.query = n.src.substring(p + 1);
 87 
 88 					t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
 89 
 90 					// If path to script is relative and a base href was found add that one infront
 91 					// the src property will always be an absolute one on non IE browsers and IE 8
 92 					// so this logic will basically only be executed on older IE versions
 93 					if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
 94 						t.baseURL = base + t.baseURL;
 95 
 96 					return t.baseURL;
 97 				}
 98 
 99 				return null;
100 			};
101 
102 			// Check document
103 			nl = d.getElementsByTagName('script');
104 			for (i=0; i<nl.length; i++) {
105 				if (getBase(nl[i]))
106 					return;
107 			}
108 
109 			// Check head
110 			n = d.getElementsByTagName('head')[0];
111 			if (n) {
112 				nl = n.getElementsByTagName('script');
113 				for (i=0; i<nl.length; i++) {
114 					if (getBase(nl[i]))
115 						return;
116 				}
117 			}
118 
119 			return;
120 		},
121 
122 		is : function(o, t) {
123 			if (!t)
124 				return o !== undef;
125 
126 			if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
127 				return true;
128 
129 			return typeof(o) == t;
130 		},
131 
132 		makeMap : function(items, delim, map) {
133 			var i;
134 
135 			items = items || [];
136 			delim = delim || ',';
137 
138 			if (typeof(items) == "string")
139 				items = items.split(delim);
140 
141 			map = map || {};
142 
143 			i = items.length;
144 			while (i--)
145 				map[items[i]] = {};
146 
147 			return map;
148 		},
149 
150 		each : function(o, cb, s) {
151 			var n, l;
152 
153 			if (!o)
154 				return 0;
155 
156 			s = s || o;
157 
158 			if (o.length !== undef) {
159 				// Indexed arrays, needed for Safari
160 				for (n=0, l = o.length; n < l; n++) {
161 					if (cb.call(s, o[n], n, o) === false)
162 						return 0;
163 				}
164 			} else {
165 				// Hashtables
166 				for (n in o) {
167 					if (o.hasOwnProperty(n)) {
168 						if (cb.call(s, o[n], n, o) === false)
169 							return 0;
170 					}
171 				}
172 			}
173 
174 			return 1;
175 		},
176 
177 
178 		map : function(a, f) {
179 			var o = [];
180 
181 			tinymce.each(a, function(v) {
182 				o.push(f(v));
183 			});
184 
185 			return o;
186 		},
187 
188 		grep : function(a, f) {
189 			var o = [];
190 
191 			tinymce.each(a, function(v) {
192 				if (!f || f(v))
193 					o.push(v);
194 			});
195 
196 			return o;
197 		},
198 
199 		inArray : function(a, v) {
200 			var i, l;
201 
202 			if (a) {
203 				for (i = 0, l = a.length; i < l; i++) {
204 					if (a[i] === v)
205 						return i;
206 				}
207 			}
208 
209 			return -1;
210 		},
211 
212 		extend : function(obj, ext) {
213 			var i, l, name, args = arguments, value;
214 
215 			for (i = 1, l = args.length; i < l; i++) {
216 				ext = args[i];
217 				for (name in ext) {
218 					if (ext.hasOwnProperty(name)) {
219 						value = ext[name];
220 
221 						if (value !== undef) {
222 							obj[name] = value;
223 						}
224 					}
225 				}
226 			}
227 
228 			return obj;
229 		},
230 
231 
232 		trim : function(s) {
233 			return (s ? '' + s : '').replace(whiteSpaceRe, '');
234 		},
235 
236 		create : function(s, p, root) {
237 			var t = this, sp, ns, cn, scn, c, de = 0;
238 
239 			// Parse : <prefix> <class>:<super class>
240 			s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
241 			cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
242 
243 			// Create namespace for new class
244 			ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
245 
246 			// Class already exists
247 			if (ns[cn])
248 				return;
249 
250 			// Make pure static class
251 			if (s[2] == 'static') {
252 				ns[cn] = p;
253 
254 				if (this.onCreate)
255 					this.onCreate(s[2], s[3], ns[cn]);
256 
257 				return;
258 			}
259 
260 			// Create default constructor
261 			if (!p[cn]) {
262 				p[cn] = function() {};
263 				de = 1;
264 			}
265 
266 			// Add constructor and methods
267 			ns[cn] = p[cn];
268 			t.extend(ns[cn].prototype, p);
269 
270 			// Extend
271 			if (s[5]) {
272 				sp = t.resolve(s[5]).prototype;
273 				scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
274 
275 				// Extend constructor
276 				c = ns[cn];
277 				if (de) {
278 					// Add passthrough constructor
279 					ns[cn] = function() {
280 						return sp[scn].apply(this, arguments);
281 					};
282 				} else {
283 					// Add inherit constructor
284 					ns[cn] = function() {
285 						this.parent = sp[scn];
286 						return c.apply(this, arguments);
287 					};
288 				}
289 				ns[cn].prototype[cn] = ns[cn];
290 
291 				// Add super methods
292 				t.each(sp, function(f, n) {
293 					ns[cn].prototype[n] = sp[n];
294 				});
295 
296 				// Add overridden methods
297 				t.each(p, function(f, n) {
298 					// Extend methods if needed
299 					if (sp[n]) {
300 						ns[cn].prototype[n] = function() {
301 							this.parent = sp[n];
302 							return f.apply(this, arguments);
303 						};
304 					} else {
305 						if (n != cn)
306 							ns[cn].prototype[n] = f;
307 					}
308 				});
309 			}
310 
311 			// Add static methods
312 			t.each(p['static'], function(f, n) {
313 				ns[cn][n] = f;
314 			});
315 
316 			if (this.onCreate)
317 				this.onCreate(s[2], s[3], ns[cn].prototype);
318 		},
319 
320 		walk : function(o, f, n, s) {
321 			s = s || this;
322 
323 			if (o) {
324 				if (n)
325 					o = o[n];
326 
327 				tinymce.each(o, function(o, i) {
328 					if (f.call(s, o, i, n) === false)
329 						return false;
330 
331 					tinymce.walk(o, f, n, s);
332 				});
333 			}
334 		},
335 
336 		createNS : function(n, o) {
337 			var i, v;
338 
339 			o = o || win;
340 
341 			n = n.split('.');
342 			for (i=0; i<n.length; i++) {
343 				v = n[i];
344 
345 				if (!o[v])
346 					o[v] = {};
347 
348 				o = o[v];
349 			}
350 
351 			return o;
352 		},
353 
354 		resolve : function(n, o) {
355 			var i, l;
356 
357 			o = o || win;
358 
359 			n = n.split('.');
360 			for (i = 0, l = n.length; i < l; i++) {
361 				o = o[n[i]];
362 
363 				if (!o)
364 					break;
365 			}
366 
367 			return o;
368 		},
369 
370 		addUnload : function(f, s) {
371 			var t = this, unload;
372 
373 			unload = function() {
374 				var li = t.unloads, o, n;
375 
376 				if (li) {
377 					// Call unload handlers
378 					for (n in li) {
379 						o = li[n];
380 
381 						if (o && o.func)
382 							o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
383 					}
384 
385 					// Detach unload function
386 					if (win.detachEvent) {
387 						win.detachEvent('onbeforeunload', fakeUnload);
388 						win.detachEvent('onunload', unload);
389 					} else if (win.removeEventListener)
390 						win.removeEventListener('unload', unload, false);
391 
392 					// Destroy references
393 					t.unloads = o = li = w = unload = 0;
394 
395 					// Run garbarge collector on IE
396 					if (win.CollectGarbage)
397 						CollectGarbage();
398 				}
399 			};
400 
401 			function fakeUnload() {
402 				var d = document;
403 
404 				function stop() {
405 					// Prevent memory leak
406 					d.detachEvent('onstop', stop);
407 
408 					// Call unload handler
409 					if (unload)
410 						unload();
411 
412 					d = 0;
413 				};
414 
415 				// Is there things still loading, then do some magic
416 				if (d.readyState == 'interactive') {
417 					// Fire unload when the currently loading page is stopped
418 					if (d)
419 						d.attachEvent('onstop', stop);
420 
421 					// Remove onstop listener after a while to prevent the unload function
422 					// to execute if the user presses cancel in an onbeforeunload
423 					// confirm dialog and then presses the browser stop button
424 					win.setTimeout(function() {
425 						if (d)
426 							d.detachEvent('onstop', stop);
427 					}, 0);
428 				}
429 			};
430 
431 			f = {func : f, scope : s || this};
432 
433 			if (!t.unloads) {
434 				// Attach unload handler
435 				if (win.attachEvent) {
436 					win.attachEvent('onunload', unload);
437 					win.attachEvent('onbeforeunload', fakeUnload);
438 				} else if (win.addEventListener)
439 					win.addEventListener('unload', unload, false);
440 
441 				// Setup initial unload handler array
442 				t.unloads = [f];
443 			} else
444 				t.unloads.push(f);
445 
446 			return f;
447 		},
448 
449 		removeUnload : function(f) {
450 			var u = this.unloads, r = null;
451 
452 			tinymce.each(u, function(o, i) {
453 				if (o && o.func == f) {
454 					u.splice(i, 1);
455 					r = f;
456 					return false;
457 				}
458 			});
459 
460 			return r;
461 		},
462 
463 		explode : function(s, d) {
464 			if (!s || tinymce.is(s, 'array')) {
465 				return s;
466 			}
467 
468 			return tinymce.map(s.split(d || ','), tinymce.trim);
469 		},
470 
471 		_addVer : function(u) {
472 			var v;
473 
474 			if (!this.query)
475 				return u;
476 
477 			v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
478 
479 			if (u.indexOf('#') == -1)
480 				return u + v;
481 
482 			return u.replace('#', v + '#');
483 		},
484 
485 		// Fix function for IE 9 where regexps isn't working correctly
486 		// Todo: remove me once MS fixes the bug
487 		_replace : function(find, replace, str) {
488 			// On IE9 we have to fake $x replacement
489 			if (isRegExpBroken) {
490 				return str.replace(find, function() {
491 					var val = replace, args = arguments, i;
492 
493 					for (i = 0; i < args.length - 2; i++) {
494 						if (args[i] === undef) {
495 							val = val.replace(new RegExp('\\$' + i, 'g'), '');
496 						} else {
497 							val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
498 						}
499 					}
500 
501 					return val;
502 				});
503 			}
504 
505 			return str.replace(find, replace);
506 		}
507 
508 		};
509 
510 	// Initialize the API
511 	tinymce._init();
512 
513 	// Expose tinymce namespace to the global namespace (window)
514 	win.tinymce = win.tinyMCE = tinymce;
515 
516 	// Describe the different namespaces
517 
518 	})(window);
519 
520 
521 
522 tinymce.create('tinymce.util.Dispatcher', {
523 	scope : null,
524 	listeners : null,
525 	inDispatch: false,
526 
527 	Dispatcher : function(scope) {
528 		this.scope = scope || this;
529 		this.listeners = [];
530 	},
531 
532 	add : function(callback, scope) {
533 		this.listeners.push({cb : callback, scope : scope || this.scope});
534 
535 		return callback;
536 	},
537 
538 	addToTop : function(callback, scope) {
539 		var self = this, listener = {cb : callback, scope : scope || self.scope};
540 
541 		// Create new listeners if addToTop is executed in a dispatch loop
542 		if (self.inDispatch) {
543 			self.listeners = [listener].concat(self.listeners);
544 		} else {
545 			self.listeners.unshift(listener);
546 		}
547 
548 		return callback;
549 	},
550 
551 	remove : function(callback) {
552 		var listeners = this.listeners, output = null;
553 
554 		tinymce.each(listeners, function(listener, i) {
555 			if (callback == listener.cb) {
556 				output = listener;
557 				listeners.splice(i, 1);
558 				return false;
559 			}
560 		});
561 
562 		return output;
563 	},
564 
565 	dispatch : function() {
566 		var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
567 
568 		self.inDispatch = true;
569 		
570 		// Needs to be a real loop since the listener count might change while looping
571 		// And this is also more efficient
572 		for (i = 0; i < listeners.length; i++) {
573 			listener = listeners[i];
574 			returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
575 
576 			if (returnValue === false)
577 				break;
578 		}
579 
580 		self.inDispatch = false;
581 
582 		return returnValue;
583 	}
584 
585 	});
586 
587 (function() {
588 	var each = tinymce.each;
589 
590 	tinymce.create('tinymce.util.URI', {
591 		URI : function(u, s) {
592 			var t = this, o, a, b, base_url;
593 
594 			// Trim whitespace
595 			u = tinymce.trim(u);
596 
597 			// Default settings
598 			s = t.settings = s || {};
599 
600 			// Strange app protocol that isn't http/https or local anchor
601 			// For example: mailto,skype,tel etc.
602 			if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
603 				t.source = u;
604 				return;
605 			}
606 
607 			// Absolute path with no host, fake host and protocol
608 			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
609 				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
610 
611 			// Relative path http:// or protocol relative //path
612 			if (!/^[\w\-]*:?\/\//.test(u)) {
613 				base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
614 				u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
615 			}
616 
617 			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
618 			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
619 			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
620 			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
621 				var s = u[i];
622 
623 				// Zope 3 workaround, they use @@something
624 				if (s)
625 					s = s.replace(/\(mce_at\)/g, '@@');
626 
627 				t[v] = s;
628 			});
629 
630 			b = s.base_uri;
631 			if (b) {
632 				if (!t.protocol)
633 					t.protocol = b.protocol;
634 
635 				if (!t.userInfo)
636 					t.userInfo = b.userInfo;
637 
638 				if (!t.port && t.host === 'mce_host')
639 					t.port = b.port;
640 
641 				if (!t.host || t.host === 'mce_host')
642 					t.host = b.host;
643 
644 				t.source = '';
645 			}
646 
647 			//t.path = t.path || '/';
648 		},
649 
650 		setPath : function(p) {
651 			var t = this;
652 
653 			p = /^(.*?)\/?(\w+)?$/.exec(p);
654 
655 			// Update path parts
656 			t.path = p[0];
657 			t.directory = p[1];
658 			t.file = p[2];
659 
660 			// Rebuild source
661 			t.source = '';
662 			t.getURI();
663 		},
664 
665 		toRelative : function(u) {
666 			var t = this, o;
667 
668 			if (u === "./")
669 				return u;
670 
671 			u = new tinymce.util.URI(u, {base_uri : t});
672 
673 			// Not on same domain/port or protocol
674 			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
675 				return u.getURI();
676 
677 			var tu = t.getURI(), uu = u.getURI();
678 			
679 			// Allow usage of the base_uri when relative_urls = true
680 			if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
681 				return tu;
682 
683 			o = t.toRelPath(t.path, u.path);
684 
685 			// Add query
686 			if (u.query)
687 				o += '?' + u.query;
688 
689 			// Add anchor
690 			if (u.anchor)
691 				o += '#' + u.anchor;
692 
693 			return o;
694 		},
695 	
696 		toAbsolute : function(u, nh) {
697 			u = new tinymce.util.URI(u, {base_uri : this});
698 
699 			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
700 		},
701 
702 		toRelPath : function(base, path) {
703 			var items, bp = 0, out = '', i, l;
704 
705 			// Split the paths
706 			base = base.substring(0, base.lastIndexOf('/'));
707 			base = base.split('/');
708 			items = path.split('/');
709 
710 			if (base.length >= items.length) {
711 				for (i = 0, l = base.length; i < l; i++) {
712 					if (i >= items.length || base[i] != items[i]) {
713 						bp = i + 1;
714 						break;
715 					}
716 				}
717 			}
718 
719 			if (base.length < items.length) {
720 				for (i = 0, l = items.length; i < l; i++) {
721 					if (i >= base.length || base[i] != items[i]) {
722 						bp = i + 1;
723 						break;
724 					}
725 				}
726 			}
727 
728 			if (bp === 1)
729 				return path;
730 
731 			for (i = 0, l = base.length - (bp - 1); i < l; i++)
732 				out += "../";
733 
734 			for (i = bp - 1, l = items.length; i < l; i++) {
735 				if (i != bp - 1)
736 					out += "/" + items[i];
737 				else
738 					out += items[i];
739 			}
740 
741 			return out;
742 		},
743 
744 		toAbsPath : function(base, path) {
745 			var i, nb = 0, o = [], tr, outPath;
746 
747 			// Split paths
748 			tr = /\/$/.test(path) ? '/' : '';
749 			base = base.split('/');
750 			path = path.split('/');
751 
752 			// Remove empty chunks
753 			each(base, function(k) {
754 				if (k)
755 					o.push(k);
756 			});
757 
758 			base = o;
759 
760 			// Merge relURLParts chunks
761 			for (i = path.length - 1, o = []; i >= 0; i--) {
762 				// Ignore empty or .
763 				if (path[i].length === 0 || path[i] === ".")
764 					continue;
765 
766 				// Is parent
767 				if (path[i] === '..') {
768 					nb++;
769 					continue;
770 				}
771 
772 				// Move up
773 				if (nb > 0) {
774 					nb--;
775 					continue;
776 				}
777 
778 				o.push(path[i]);
779 			}
780 
781 			i = base.length - nb;
782 
783 			// If /a/b/c or /
784 			if (i <= 0)
785 				outPath = o.reverse().join('/');
786 			else
787 				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
788 
789 			// Add front / if it's needed
790 			if (outPath.indexOf('/') !== 0)
791 				outPath = '/' + outPath;
792 
793 			// Add traling / if it's needed
794 			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
795 				outPath += tr;
796 
797 			return outPath;
798 		},
799 
800 		getURI : function(nh) {
801 			var s, t = this;
802 
803 			// Rebuild source
804 			if (!t.source || nh) {
805 				s = '';
806 
807 				if (!nh) {
808 					if (t.protocol)
809 						s += t.protocol + '://';
810 
811 					if (t.userInfo)
812 						s += t.userInfo + '@';
813 
814 					if (t.host)
815 						s += t.host;
816 
817 					if (t.port)
818 						s += ':' + t.port;
819 				}
820 
821 				if (t.path)
822 					s += t.path;
823 
824 				if (t.query)
825 					s += '?' + t.query;
826 
827 				if (t.anchor)
828 					s += '#' + t.anchor;
829 
830 				t.source = s;
831 			}
832 
833 			return t.source;
834 		}
835 	});
836 })();
837 
838 (function() {
839 	var each = tinymce.each;
840 
841 	tinymce.create('static tinymce.util.Cookie', {
842 		getHash : function(n) {
843 			var v = this.get(n), h;
844 
845 			if (v) {
846 				each(v.split('&'), function(v) {
847 					v = v.split('=');
848 					h = h || {};
849 					h[unescape(v[0])] = unescape(v[1]);
850 				});
851 			}
852 
853 			return h;
854 		},
855 
856 		setHash : function(n, v, e, p, d, s) {
857 			var o = '';
858 
859 			each(v, function(v, k) {
860 				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
861 			});
862 
863 			this.set(n, o, e, p, d, s);
864 		},
865 
866 		get : function(n) {
867 			var c = document.cookie, e, p = n + "=", b;
868 
869 			// Strict mode
870 			if (!c)
871 				return;
872 
873 			b = c.indexOf("; " + p);
874 
875 			if (b == -1) {
876 				b = c.indexOf(p);
877 
878 				if (b !== 0)
879 					return null;
880 			} else
881 				b += 2;
882 
883 			e = c.indexOf(";", b);
884 
885 			if (e == -1)
886 				e = c.length;
887 
888 			return unescape(c.substring(b + p.length, e));
889 		},
890 
891 		set : function(n, v, e, p, d, s) {
892 			document.cookie = n + "=" + escape(v) +
893 				((e) ? "; expires=" + e.toGMTString() : "") +
894 				((p) ? "; path=" + escape(p) : "") +
895 				((d) ? "; domain=" + d : "") +
896 				((s) ? "; secure" : "");
897 		},
898 
899 		remove : function(name, path, domain) {
900 			var date = new Date();
901 
902 			date.setTime(date.getTime() - 1000);
903 
904 			this.set(name, '', date, path, domain);
905 		}
906 	});
907 })();
908 
909 (function() {
910 	function serialize(o, quote) {
911 		var i, v, t, name;
912 
913 		quote = quote || '"';
914 
915 		if (o == null)
916 			return 'null';
917 
918 		t = typeof o;
919 
920 		if (t == 'string') {
921 			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
922 
923 			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
924 				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
925 				if (quote === '"' && a === "'")
926 					return a;
927 
928 				i = v.indexOf(b);
929 
930 				if (i + 1)
931 					return '\\' + v.charAt(i + 1);
932 
933 				a = b.charCodeAt().toString(16);
934 
935 				return '\\u' + '0000'.substring(a.length) + a;
936 			}) + quote;
937 		}
938 
939 		if (t == 'object') {
940 			if (o.hasOwnProperty && o instanceof Array) {
941 					for (i=0, v = '['; i<o.length; i++)
942 						v += (i > 0 ? ',' : '') + serialize(o[i], quote);
943 
944 					return v + ']';
945 				}
946 
947 				v = '{';
948 
949 				for (name in o) {
950 					if (o.hasOwnProperty(name)) {
951 						v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
952 					}
953 				}
954 
955 				return v + '}';
956 		}
957 
958 		return '' + o;
959 	};
960 
961 	tinymce.util.JSON = {
962 		serialize: serialize,
963 
964 		parse: function(s) {
965 			try {
966 				return eval('(' + s + ')');
967 			} catch (ex) {
968 				// Ignore
969 			}
970 		}
971 
972 		};
973 })();
974 
975 tinymce.create('static tinymce.util.XHR', {
976 	send : function(o) {
977 		var x, t, w = window, c = 0;
978 
979 		function ready() {
980 			if (!o.async || x.readyState == 4 || c++ > 10000) {
981 				if (o.success && c < 10000 && x.status == 200)
982 					o.success.call(o.success_scope, '' + x.responseText, x, o);
983 				else if (o.error)
984 					o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
985 
986 				x = null;
987 			} else
988 				w.setTimeout(ready, 10);
989 		};
990 
991 		// Default settings
992 		o.scope = o.scope || this;
993 		o.success_scope = o.success_scope || o.scope;
994 		o.error_scope = o.error_scope || o.scope;
995 		o.async = o.async === false ? false : true;
996 		o.data = o.data || '';
997 
998 		function get(s) {
999 			x = 0;
1000 
1001 			try {
1002 				x = new ActiveXObject(s);
1003 			} catch (ex) {
1004 			}
1005 
1006 			return x;
1007 		};
1008 
1009 		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
1010 
1011 		if (x) {
1012 			if (x.overrideMimeType)
1013 				x.overrideMimeType(o.content_type);
1014 
1015 			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1016 
1017 			if (o.content_type)
1018 				x.setRequestHeader('Content-Type', o.content_type);
1019 
1020 			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1021 
1022 			x.send(o.data);
1023 
1024 			// Syncronous request
1025 			if (!o.async)
1026 				return ready();
1027 
1028 			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1029 			t = w.setTimeout(ready, 10);
1030 		}
1031 	}
1032 });
1033 
1034 (function() {
1035 	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1036 
1037 	tinymce.create('tinymce.util.JSONRequest', {
1038 		JSONRequest : function(s) {
1039 			this.settings = extend({
1040 			}, s);
1041 			this.count = 0;
1042 		},
1043 
1044 		send : function(o) {
1045 			var ecb = o.error, scb = o.success;
1046 
1047 			o = extend(this.settings, o);
1048 
1049 			o.success = function(c, x) {
1050 				c = JSON.parse(c);
1051 
1052 				if (typeof(c) == 'undefined') {
1053 					c = {
1054 						error : 'JSON Parse error.'
1055 					};
1056 				}
1057 
1058 				if (c.error)
1059 					ecb.call(o.error_scope || o.scope, c.error, x);
1060 				else
1061 					scb.call(o.success_scope || o.scope, c.result);
1062 			};
1063 
1064 			o.error = function(ty, x) {
1065 				if (ecb)
1066 					ecb.call(o.error_scope || o.scope, ty, x);
1067 			};
1068 
1069 			o.data = JSON.serialize({
1070 				id : o.id || 'c' + (this.count++),
1071 				method : o.method,
1072 				params : o.params
1073 			});
1074 
1075 			// JSON content type for Ruby on rails. Bug: #1883287
1076 			o.content_type = 'application/json';
1077 
1078 			XHR.send(o);
1079 		},
1080 
1081 		'static' : {
1082 			sendRPC : function(o) {
1083 				return new tinymce.util.JSONRequest().send(o);
1084 			}
1085 		}
1086 	});
1087 }());
1088 (function(tinymce){
1089 	tinymce.VK = {
1090 		BACKSPACE: 8,
1091 		DELETE: 46,
1092 		DOWN: 40,
1093 		ENTER: 13,
1094 		LEFT: 37,
1095 		RIGHT: 39,
1096 		SPACEBAR: 32,
1097 		TAB: 9,
1098 		UP: 38,
1099 
1100 		modifierPressed: function (e) {
1101 			return e.shiftKey || e.ctrlKey || e.altKey;
1102 		},
1103 
1104 		metaKeyPressed: function(e) {
1105 			return tinymce.isMac ? e.metaKey : e.ctrlKey;
1106 		}
1107 	};
1108 })(tinymce);
1109 
1110 tinymce.util.Quirks = function(editor) {
1111 	var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings;
1112 
1113 	function setEditorCommandState(cmd, state) {
1114 		try {
1115 			editor.getDoc().execCommand(cmd, false, state);
1116 		} catch (ex) {
1117 			// Ignore
1118 		}
1119 	}
1120 
1121 	function getDocumentMode() {
1122 		var documentMode = editor.getDoc().documentMode;
1123 
1124 		return documentMode ? documentMode : 6;
1125 	};
1126 
1127 	function cleanupStylesWhenDeleting() {
1128 		function removeMergedFormatSpans(isDelete) {
1129 			var rng, blockElm, node, clonedSpan;
1130 
1131 			rng = selection.getRng();
1132 
1133 			// Find root block
1134 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1135 
1136 			// On delete clone the root span of the next block element
1137 			if (isDelete)
1138 				blockElm = dom.getNext(blockElm, dom.isBlock);
1139 
1140 			// Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1141 			if (blockElm) {
1142 				node = blockElm.firstChild;
1143 
1144 				// Ignore empty text nodes
1145 				while (node && node.nodeType == 3 && node.nodeValue.length === 0)
1146 					node = node.nextSibling;
1147 
1148 				if (node && node.nodeName === 'SPAN') {
1149 					clonedSpan = node.cloneNode(false);
1150 				}
1151 			}
1152 
1153 			// Do the backspace/delete action
1154 			editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1155 
1156 			// Find all odd apple-style-spans
1157 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1158 			tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1159 				var bm = selection.getBookmark();
1160 
1161 				if (clonedSpan) {
1162 					dom.replace(clonedSpan.cloneNode(false), span, true);
1163 				} else {
1164 					dom.remove(span, true);
1165 				}
1166 
1167 				// Restore the selection
1168 				selection.moveToBookmark(bm);
1169 			});
1170 		};
1171 
1172 		editor.onKeyDown.add(function(editor, e) {
1173 			var isDelete;
1174 
1175 			isDelete = e.keyCode == DELETE;
1176 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1177 				e.preventDefault();
1178 				removeMergedFormatSpans(isDelete);
1179 			}
1180 		});
1181 
1182 		editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1183 	};
1184 	
1185 	function emptyEditorWhenDeleting() {
1186 		function serializeRng(rng) {
1187 			var body = dom.create("body");
1188 			var contents = rng.cloneContents();
1189 			body.appendChild(contents);
1190 			return selection.serializer.serialize(body, {format: 'html'});
1191 		}
1192 
1193 		function allContentsSelected(rng) {
1194 			var selection = serializeRng(rng);
1195 
1196 			var allRng = dom.createRng();
1197 			allRng.selectNode(editor.getBody());
1198 
1199 			var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection);
1200 			return selection === allSelection;
1201 		}
1202 
1203 		editor.onKeyDown.add(function(editor, e) {
1204 			var keyCode = e.keyCode, isCollapsed;
1205 
1206 			// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1207 			if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
1208 				isCollapsed = editor.selection.isCollapsed();
1209 
1210 				// Selection is collapsed but the editor isn't empty
1211 				if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1212 					return;
1213 				}
1214 
1215 				// IE deletes all contents correctly when everything is selected
1216 				if (tinymce.isIE && !isCollapsed) {
1217 					return;
1218 				}
1219 
1220 				// Selection isn't collapsed but not all the contents is selected
1221 				if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1222 					return;
1223 				}
1224 
1225 				// Manually empty the editor
1226 				editor.setContent('');
1227 				editor.selection.setCursorLocation(editor.getBody(), 0);
1228 				editor.nodeChanged();
1229 			}
1230 		});
1231 	};
1232 
1233 	function selectAll() {
1234 		editor.onKeyDown.add(function(editor, e) {
1235 			if (e.keyCode == 65 && VK.metaKeyPressed(e)) {
1236 				e.preventDefault();
1237 				editor.execCommand('SelectAll');
1238 			}
1239 		});
1240 	};
1241 
1242 	function inputMethodFocus() {
1243 		if (!editor.settings.content_editable) {
1244 			// Case 1 IME doesn't initialize if you focus the document
1245 			dom.bind(editor.getDoc(), 'focusin', function(e) {
1246 				selection.setRng(selection.getRng());
1247 			});
1248 
1249 			// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1250 			dom.bind(editor.getDoc(), 'mousedown', function(e) {
1251 				if (e.target == editor.getDoc().documentElement) {
1252 					editor.getWin().focus();
1253 					selection.setRng(selection.getRng());
1254 				}
1255 			});
1256 		}
1257 	};
1258 
1259 	function removeHrOnBackspace() {
1260 		editor.onKeyDown.add(function(editor, e) {
1261 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1262 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1263 					var node = selection.getNode();
1264 					var previousSibling = node.previousSibling;
1265 
1266 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1267 						dom.remove(previousSibling);
1268 						tinymce.dom.Event.cancel(e);
1269 					}
1270 				}
1271 			}
1272 		})
1273 	}
1274 
1275 	function focusBody() {
1276 		// Fix for a focus bug in FF 3.x where the body element
1277 		// wouldn't get proper focus if the user clicked on the HTML element
1278 		if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1279 			editor.onMouseDown.add(function(editor, e) {
1280 				if (e.target.nodeName === "HTML") {
1281 					var body = editor.getBody();
1282 
1283 					// Blur the body it's focused but not correctly focused
1284 					body.blur();
1285 
1286 					// Refocus the body after a little while
1287 					setTimeout(function() {
1288 						body.focus();
1289 					}, 0);
1290 				}
1291 			});
1292 		}
1293 	};
1294 
1295 	function selectControlElements() {
1296 		editor.onClick.add(function(editor, e) {
1297 			e = e.target;
1298 
1299 			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1300 			// WebKit can't even do simple things like selecting an image
1301 			// Needs tobe the setBaseAndExtend or it will fail to select floated images
1302 			if (/^(IMG|HR)$/.test(e.nodeName)) {
1303 				selection.getSel().setBaseAndExtent(e, 0, e, 1);
1304 			}
1305 
1306 			if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1307 				selection.select(e);
1308 			}
1309 
1310 			editor.nodeChanged();
1311 		});
1312 	};
1313 
1314 	function removeStylesWhenDeletingAccrossBlockElements() {
1315 		function getAttributeApplyFunction() {
1316 			var template = dom.getAttribs(selection.getStart().cloneNode(false));
1317 
1318 			return function() {
1319 				var target = selection.getStart();
1320 
1321 				if (target !== editor.getBody()) {
1322 					dom.setAttrib(target, "style", null);
1323 
1324 					tinymce.each(template, function(attr) {
1325 						target.setAttributeNode(attr.cloneNode(true));
1326 					});
1327 				}
1328 			};
1329 		}
1330 
1331 		function isSelectionAcrossElements() {
1332 			return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
1333 		}
1334 
1335 		function blockEvent(editor, e) {
1336 			e.preventDefault();
1337 			return false;
1338 		}
1339 
1340 		editor.onKeyPress.add(function(editor, e) {
1341 			var applyAttributes;
1342 
1343 			if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1344 				applyAttributes = getAttributeApplyFunction();
1345 				editor.getDoc().execCommand('delete', false, null);
1346 				applyAttributes();
1347 				e.preventDefault();
1348 				return false;
1349 			}
1350 		});
1351 
1352 		dom.bind(editor.getDoc(), 'cut', function(e) {
1353 			var applyAttributes;
1354 
1355 			if (isSelectionAcrossElements()) {
1356 				applyAttributes = getAttributeApplyFunction();
1357 				editor.onKeyUp.addToTop(blockEvent);
1358 
1359 				setTimeout(function() {
1360 					applyAttributes();
1361 					editor.onKeyUp.remove(blockEvent);
1362 				}, 0);
1363 			}
1364 		});
1365 	}
1366 
1367 	function selectionChangeNodeChanged() {
1368 		var lastRng, selectionTimer;
1369 
1370 		dom.bind(editor.getDoc(), 'selectionchange', function() {
1371 			if (selectionTimer) {
1372 				clearTimeout(selectionTimer);
1373 				selectionTimer = 0;
1374 			}
1375 
1376 			selectionTimer = window.setTimeout(function() {
1377 				var rng = selection.getRng();
1378 
1379 				// Compare the ranges to see if it was a real change or not
1380 				if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1381 					editor.nodeChanged();
1382 					lastRng = rng;
1383 				}
1384 			}, 50);
1385 		});
1386 	}
1387 
1388 	function ensureBodyHasRoleApplication() {
1389 		document.body.setAttribute("role", "application");
1390 	}
1391 
1392 	function disableBackspaceIntoATable() {
1393 		editor.onKeyDown.add(function(editor, e) {
1394 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1395 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1396 					var previousSibling = selection.getNode().previousSibling;
1397 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1398 						return tinymce.dom.Event.cancel(e);
1399 					}
1400 				}
1401 			}
1402 		})
1403 	}
1404 
1405 	function addNewLinesBeforeBrInPre() {
1406 		// IE8+ rendering mode does the right thing with BR in PRE
1407 		if (getDocumentMode() > 7) {
1408 			return;
1409 		}
1410 
1411 		 // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1412 		 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1413 		setEditorCommandState('RespectVisibilityInDesign', true);
1414 		editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1415 		dom.addClass(editor.getBody(), 'mceHideBrInPre');
1416 
1417 		// Adds a \n before all BR elements in PRE to get them visual
1418 		editor.parser.addNodeFilter('pre', function(nodes, name) {
1419 			var i = nodes.length, brNodes, j, brElm, sibling;
1420 
1421 			while (i--) {
1422 				brNodes = nodes[i].getAll('br');
1423 				j = brNodes.length;
1424 				while (j--) {
1425 					brElm = brNodes[j];
1426 
1427 					// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1428 					sibling = brElm.prev;
1429 					if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1430 						sibling.value += '\n';
1431 					} else {
1432 						brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1433 					}
1434 				}
1435 			}
1436 		});
1437 
1438 		// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1439 		editor.serializer.addNodeFilter('pre', function(nodes, name) {
1440 			var i = nodes.length, brNodes, j, brElm, sibling;
1441 
1442 			while (i--) {
1443 				brNodes = nodes[i].getAll('br');
1444 				j = brNodes.length;
1445 				while (j--) {
1446 					brElm = brNodes[j];
1447 					sibling = brElm.prev;
1448 					if (sibling && sibling.type == 3) {
1449 						sibling.value = sibling.value.replace(/\r?\n$/, '');
1450 					}
1451 				}
1452 			}
1453 		});
1454 	}
1455 
1456 	function removePreSerializedStylesWhenSelectingControls() {
1457 		dom.bind(editor.getBody(), 'mouseup', function(e) {
1458 			var value, node = selection.getNode();
1459 
1460 			// Moved styles to attributes on IMG eements
1461 			if (node.nodeName == 'IMG') {
1462 				// Convert style width to width attribute
1463 				if (value = dom.getStyle(node, 'width')) {
1464 					dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1465 					dom.setStyle(node, 'width', '');
1466 				}
1467 
1468 				// Convert style height to height attribute
1469 				if (value = dom.getStyle(node, 'height')) {
1470 					dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1471 					dom.setStyle(node, 'height', '');
1472 				}
1473 			}
1474 		});
1475 	}
1476 
1477 	function keepInlineElementOnDeleteBackspace() {
1478 		editor.onKeyDown.add(function(editor, e) {
1479 			var isDelete, rng, container, offset, brElm, sibling, collapsed;
1480 
1481 			isDelete = e.keyCode == DELETE;
1482 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1483 				rng = selection.getRng();
1484 				container = rng.startContainer;
1485 				offset = rng.startOffset;
1486 				collapsed = rng.collapsed;
1487 
1488 				// Override delete if the start container is a text node and is at the beginning of text or
1489 				// just before/after the last character to be deleted in collapsed mode
1490 				if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1491 					nonEmptyElements = editor.schema.getNonEmptyElements();
1492 
1493 					// Prevent default logic since it's broken
1494 					e.preventDefault();
1495 
1496 					// Insert a BR before the text node this will prevent the containing element from being deleted/converted
1497 					brElm = dom.create('br', {id: '__tmp'});
1498 					container.parentNode.insertBefore(brElm, container);
1499 
1500 					// Do the browser delete
1501 					editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1502 
1503 					// Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1504 					container = selection.getRng().startContainer;
1505 					sibling = container.previousSibling;
1506 					if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1507 						dom.remove(sibling);
1508 					}
1509 
1510 					// Remove the temp element we inserted
1511 					dom.remove('__tmp');
1512 				}
1513 			}
1514 		});
1515 	}
1516 
1517 	function removeBlockQuoteOnBackSpace() {
1518 		// Add block quote deletion handler
1519 		editor.onKeyDown.add(function(editor, e) {
1520 			var rng, container, offset, root, parent;
1521 
1522 			if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {
1523 				return;
1524 			}
1525 
1526 			rng = selection.getRng();
1527 			container = rng.startContainer;
1528 			offset = rng.startOffset;
1529 			root = dom.getRoot();
1530 			parent = container;
1531 
1532 			if (!rng.collapsed || offset !== 0) {
1533 				return;
1534 			}
1535 
1536 			while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1537 				parent = parent.parentNode;
1538 			}
1539 
1540 			// Is the cursor at the beginning of a blockquote?
1541 			if (parent.tagName === 'BLOCKQUOTE') {
1542 				// Remove the blockquote
1543 				editor.formatter.toggle('blockquote', null, parent);
1544 
1545 				// Move the caret to the beginning of container
1546 				rng.setStart(container, 0);
1547 				rng.setEnd(container, 0);
1548 				selection.setRng(rng);
1549 				selection.collapse(false);
1550 			}
1551 		});
1552 	};
1553 
1554 	function setGeckoEditingOptions() {
1555 		function setOpts() {
1556 			editor._refreshContentEditable();
1557 
1558 			setEditorCommandState("StyleWithCSS", false);
1559 			setEditorCommandState("enableInlineTableEditing", false);
1560 
1561 			if (!settings.object_resizing) {
1562 				setEditorCommandState("enableObjectResizing", false);
1563 			}
1564 		};
1565 
1566 		if (!settings.readonly) {
1567 			editor.onBeforeExecCommand.add(setOpts);
1568 			editor.onMouseDown.add(setOpts);
1569 		}
1570 	};
1571 
1572 	function addBrAfterLastLinks() {
1573 		function fixLinks(editor, o) {
1574 			tinymce.each(dom.select('a'), function(node) {
1575 				var parentNode = node.parentNode, root = dom.getRoot();
1576 
1577 				if (parentNode.lastChild === node) {
1578 					while (parentNode && !dom.isBlock(parentNode)) {
1579 						if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1580 							return;
1581 						}
1582 
1583 						parentNode = parentNode.parentNode;
1584 					}
1585 
1586 					dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1587 				}
1588 			});
1589 		};
1590 
1591 		editor.onExecCommand.add(function(editor, cmd) {
1592 			if (cmd === 'CreateLink') {
1593 				fixLinks(editor);
1594 			}
1595 		});
1596 
1597 		editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1598 	};
1599 
1600 	function setDefaultBlockType() {
1601 		if (settings.forced_root_block) {
1602 			editor.onInit.add(function() {
1603 				setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1604 			});
1605 		}
1606 	}
1607 
1608 	function removeGhostSelection() {
1609 		function repaint(sender, args) {
1610 			if (!sender || !args.initial) {
1611 				editor.execCommand('mceRepaint');
1612 			}
1613 		};
1614 
1615 		editor.onUndo.add(repaint);
1616 		editor.onRedo.add(repaint);
1617 		editor.onSetContent.add(repaint);
1618 	};
1619 
1620 	function deleteControlItemOnBackSpace() {
1621 		editor.onKeyDown.add(function(editor, e) {
1622 			var rng;
1623 
1624 			if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) {
1625 				rng = editor.getDoc().selection.createRange();
1626 				if (rng && rng.item) {
1627 					e.preventDefault();
1628 					editor.undoManager.beforeChange();
1629 					dom.remove(rng.item(0));
1630 					editor.undoManager.add();
1631 				}
1632 			}
1633 		});
1634 	};
1635 
1636 	function renderEmptyBlocksFix() {
1637 		var emptyBlocksCSS;
1638 
1639 		// IE10+
1640 		if (getDocumentMode() >= 10) {
1641 			emptyBlocksCSS = '';
1642 			tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1643 				emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1644 			});
1645 
1646 			editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1647 		}
1648 	};
1649 
1650 	function fakeImageResize() {
1651 		var mouseDownImg, startX, startY, startW, startH;
1652 
1653 		if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1654 			return;
1655 		}
1656 
1657 		editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}');
1658 
1659 		function resizeImage(e) {
1660 			var deltaX, deltaY, ratio, width, height;
1661 
1662 			if (mouseDownImg) {
1663 				deltaX = e.screenX - startX;
1664 				deltaY = e.screenY - startY;
1665 				ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH);
1666 
1667 				// Only update styles if the user draged one pixel or more
1668 				if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
1669 					// Constrain proportions
1670 					width = Math.round(startW * ratio);
1671 					height = Math.round(startH * ratio);
1672 
1673 					// Resize by using style or attribute
1674 					if (mouseDownImg.style.width) {
1675 						dom.setStyle(mouseDownImg, 'width', width);
1676 					} else {
1677 						dom.setAttrib(mouseDownImg, 'width', width);
1678 					}
1679 
1680 					// Resize by using style or attribute
1681 					if (mouseDownImg.style.height) {
1682 						dom.setStyle(mouseDownImg, 'height', height);
1683 					} else {
1684 						dom.setAttrib(mouseDownImg, 'height', height);
1685 					}
1686 
1687 					if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) {
1688 						dom.addClass(editor.getBody(), 'mceResizeImages');
1689 					}
1690 				}
1691 			}
1692 		};
1693 
1694 		editor.onMouseDown.add(function(editor, e) {
1695 			var target = e.target;
1696 
1697 			if (target.nodeName == "IMG") {
1698 				mouseDownImg = target;
1699 				startX = e.screenX;
1700 				startY = e.screenY;
1701 				startW = mouseDownImg.clientWidth;
1702 				startH = mouseDownImg.clientHeight;
1703 				dom.bind(editor.getDoc(), 'mousemove', resizeImage);
1704 				e.preventDefault();
1705 			}
1706 		});
1707 
1708 		// Unbind events on node change and restore resize cursor
1709 		editor.onNodeChange.add(function() {
1710 			if (mouseDownImg) {
1711 				mouseDownImg = null;
1712 				dom.unbind(editor.getDoc(), 'mousemove', resizeImage);
1713 			}
1714 
1715 			if (selection.getNode().nodeName == "IMG") {
1716 				dom.addClass(editor.getBody(), 'mceResizeImages');
1717 			} else {
1718 				dom.removeClass(editor.getBody(), 'mceResizeImages');
1719 			}
1720 		});
1721 	};
1722 
1723 	// All browsers
1724 	disableBackspaceIntoATable();
1725 	removeBlockQuoteOnBackSpace();
1726 	emptyEditorWhenDeleting();
1727 
1728 	// WebKit
1729 	if (tinymce.isWebKit) {
1730 		keepInlineElementOnDeleteBackspace();
1731 		cleanupStylesWhenDeleting();
1732 		inputMethodFocus();
1733 		selectControlElements();
1734 		setDefaultBlockType();
1735 
1736 		// iOS
1737 		if (tinymce.isIDevice) {
1738 			selectionChangeNodeChanged();
1739 		} else {
1740 			fakeImageResize();
1741 			selectAll();
1742 		}
1743 	}
1744 
1745 	// IE
1746 	if (tinymce.isIE) {
1747 		removeHrOnBackspace();
1748 		ensureBodyHasRoleApplication();
1749 		addNewLinesBeforeBrInPre();
1750 		removePreSerializedStylesWhenSelectingControls();
1751 		deleteControlItemOnBackSpace();
1752 		renderEmptyBlocksFix();
1753 	}
1754 
1755 	// Gecko
1756 	if (tinymce.isGecko) {
1757 		removeHrOnBackspace();
1758 		focusBody();
1759 		removeStylesWhenDeletingAccrossBlockElements();
1760 		setGeckoEditingOptions();
1761 		addBrAfterLastLinks();
1762 		removeGhostSelection();
1763 	}
1764 };
1765 (function(tinymce) {
1766 	var namedEntities, baseEntities, reverseEntities,
1767 		attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1768 		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1769 		rawCharsRegExp = /[<>&\"\']/g,
1770 		entityRegExp = /&(#x|#)?([\w]+);/g,
1771 		asciiMap = {
1772 				128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1773 				135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1774 				142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1775 				150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1776 				156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1777 		};
1778 
1779 	// Raw entities
1780 	baseEntities = {
1781 		'\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code
1782 		"'" : ''',
1783 		'<' : '<',
1784 		'>' : '>',
1785 		'&' : '&'
1786 	};
1787 
1788 	// Reverse lookup table for raw entities
1789 	reverseEntities = {
1790 		'<' : '<',
1791 		'>' : '>',
1792 		'&' : '&',
1793 		'"' : '"',
1794 		''' : "'"
1795 	};
1796 
1797 	// Decodes text by using the browser
1798 	function nativeDecode(text) {
1799 		var elm;
1800 
1801 		elm = document.createElement("div");
1802 		elm.innerHTML = text;
1803 
1804 		return elm.textContent || elm.innerText || text;
1805 	};
1806 
1807 	// Build a two way lookup table for the entities
1808 	function buildEntitiesLookup(items, radix) {
1809 		var i, chr, entity, lookup = {};
1810 
1811 		if (items) {
1812 			items = items.split(',');
1813 			radix = radix || 10;
1814 
1815 			// Build entities lookup table
1816 			for (i = 0; i < items.length; i += 2) {
1817 				chr = String.fromCharCode(parseInt(items[i], radix));
1818 
1819 				// Only add non base entities
1820 				if (!baseEntities[chr]) {
1821 					entity = '&' + items[i + 1] + ';';
1822 					lookup[chr] = entity;
1823 					lookup[entity] = chr;
1824 				}
1825 			}
1826 
1827 			return lookup;
1828 		}
1829 	};
1830 
1831 	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
1832 	namedEntities = buildEntitiesLookup(
1833 		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1834 		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1835 		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1836 		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1837 		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1838 		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1839 		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1840 		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1841 		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1842 		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1843 		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1844 		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1845 		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1846 		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1847 		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1848 		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1849 		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1850 		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1851 		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1852 		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1853 		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1854 		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1855 		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1856 		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1857 		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
1858 
1859 	tinymce.html = tinymce.html || {};
1860 
1861 	tinymce.html.Entities = {
1862 		encodeRaw : function(text, attr) {
1863 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1864 				return baseEntities[chr] || chr;
1865 			});
1866 		},
1867 
1868 		encodeAllRaw : function(text) {
1869 			return ('' + text).replace(rawCharsRegExp, function(chr) {
1870 				return baseEntities[chr] || chr;
1871 			});
1872 		},
1873 
1874 		encodeNumeric : function(text, attr) {
1875 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1876 				// Multi byte sequence convert it to a single entity
1877 				if (chr.length > 1)
1878 					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1879 
1880 				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1881 			});
1882 		},
1883 
1884 		encodeNamed : function(text, attr, entities) {
1885 			entities = entities || namedEntities;
1886 
1887 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1888 				return baseEntities[chr] || entities[chr] || chr;
1889 			});
1890 		},
1891 
1892 		getEncodeFunc : function(name, entities) {
1893 			var Entities = tinymce.html.Entities;
1894 
1895 			entities = buildEntitiesLookup(entities) || namedEntities;
1896 
1897 			function encodeNamedAndNumeric(text, attr) {
1898 				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1899 					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1900 				});
1901 			};
1902 
1903 			function encodeCustomNamed(text, attr) {
1904 				return Entities.encodeNamed(text, attr, entities);
1905 			};
1906 
1907 			// Replace + with , to be compatible with previous TinyMCE versions
1908 			name = tinymce.makeMap(name.replace(/\+/g, ','));
1909 
1910 			// Named and numeric encoder
1911 			if (name.named && name.numeric)
1912 				return encodeNamedAndNumeric;
1913 
1914 			// Named encoder
1915 			if (name.named) {
1916 				// Custom names
1917 				if (entities)
1918 					return encodeCustomNamed;
1919 
1920 				return Entities.encodeNamed;
1921 			}
1922 
1923 			// Numeric
1924 			if (name.numeric)
1925 				return Entities.encodeNumeric;
1926 
1927 			// Raw encoder
1928 			return Entities.encodeRaw;
1929 		},
1930 
1931 		decode : function(text) {
1932 			return text.replace(entityRegExp, function(all, numeric, value) {
1933 				if (numeric) {
1934 					value = parseInt(value, numeric.length === 2 ? 16 : 10);
1935 
1936 					// Support upper UTF
1937 					if (value > 0xFFFF) {
1938 						value -= 0x10000;
1939 
1940 						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1941 					} else
1942 						return asciiMap[value] || String.fromCharCode(value);
1943 				}
1944 
1945 				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1946 			});
1947 		}
1948 	};
1949 })(tinymce);
1950 
1951 tinymce.html.Styles = function(settings, schema) {
1952 	var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1953 		urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1954 		styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1955 		trimRightRegExp = /\s+$/,
1956 		urlColorRegExp = /rgb/,
1957 		undef, i, encodingLookup = {}, encodingItems;
1958 
1959 	settings = settings || {};
1960 
1961 	encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
1962 	for (i = 0; i < encodingItems.length; i++) {
1963 		encodingLookup[encodingItems[i]] = '\uFEFF' + i;
1964 		encodingLookup['\uFEFF' + i] = encodingItems[i];
1965 	}
1966 
1967 	function toHex(match, r, g, b) {
1968 		function hex(val) {
1969 			val = parseInt(val).toString(16);
1970 
1971 			return val.length > 1 ? val : '0' + val; // 0 -> 00
1972 		};
1973 
1974 		return '#' + hex(r) + hex(g) + hex(b);
1975 	};
1976 
1977 	return {
1978 		toHex : function(color) {
1979 			return color.replace(rgbRegExp, toHex);
1980 		},
1981 
1982 		parse : function(css) {
1983 			var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1984 
1985 			function compress(prefix, suffix) {
1986 				var top, right, bottom, left;
1987 
1988 				// Get values and check it it needs compressing
1989 				top = styles[prefix + '-top' + suffix];
1990 				if (!top)
1991 					return;
1992 
1993 				right = styles[prefix + '-right' + suffix];
1994 				if (top != right)
1995 					return;
1996 
1997 				bottom = styles[prefix + '-bottom' + suffix];
1998 				if (right != bottom)
1999 					return;
2000 
2001 				left = styles[prefix + '-left' + suffix];
2002 				if (bottom != left)
2003 					return;
2004 
2005 				// Compress
2006 				styles[prefix + suffix] = left;
2007 				delete styles[prefix + '-top' + suffix];
2008 				delete styles[prefix + '-right' + suffix];
2009 				delete styles[prefix + '-bottom' + suffix];
2010 				delete styles[prefix + '-left' + suffix];
2011 			};
2012 
2013 			function canCompress(key) {
2014 				var value = styles[key], i;
2015 
2016 				if (!value || value.indexOf(' ') < 0)
2017 					return;
2018 
2019 				value = value.split(' ');
2020 				i = value.length;
2021 				while (i--) {
2022 					if (value[i] !== value[0])
2023 						return false;
2024 				}
2025 
2026 				styles[key] = value[0];
2027 
2028 				return true;
2029 			};
2030 
2031 			function compress2(target, a, b, c) {
2032 				if (!canCompress(a))
2033 					return;
2034 
2035 				if (!canCompress(b))
2036 					return;
2037 
2038 				if (!canCompress(c))
2039 					return;
2040 
2041 				// Compress
2042 				styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2043 				delete styles[a];
2044 				delete styles[b];
2045 				delete styles[c];
2046 			};
2047 
2048 			// Encodes the specified string by replacing all \" \' ; : with _<num>
2049 			function encode(str) {
2050 				isEncoded = true;
2051 
2052 				return encodingLookup[str];
2053 			};
2054 
2055 			// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2056 			// It will also decode the \" \' if keep_slashes is set to fale or omitted
2057 			function decode(str, keep_slashes) {
2058 				if (isEncoded) {
2059 					str = str.replace(/\uFEFF[0-9]/g, function(str) {
2060 						return encodingLookup[str];
2061 					});
2062 				}
2063 
2064 				if (!keep_slashes)
2065 					str = str.replace(/\\([\'\";:])/g, "$1");
2066 
2067 				return str;
2068 			};
2069 
2070 			function processUrl(match, url, url2, url3, str, str2) {
2071 				str = str || str2;
2072 
2073 				if (str) {
2074 					str = decode(str);
2075 
2076 					// Force strings into single quote format
2077 					return "'" + str.replace(/\'/g, "\\'") + "'";
2078 				}
2079 
2080 				url = decode(url || url2 || url3);
2081 
2082 				// Convert the URL to relative/absolute depending on config
2083 				if (urlConverter)
2084 					url = urlConverter.call(urlConverterScope, url, 'style');
2085 
2086 				// Output new URL format
2087 				return "url('" + url.replace(/\'/g, "\\'") + "')";
2088 			};
2089 
2090 			if (css) {
2091 				// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2092 				css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2093 					return str.replace(/[;:]/g, encode);
2094 				});
2095 
2096 				// Parse styles
2097 				while (matches = styleRegExp.exec(css)) {
2098 					name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2099 					value = matches[2].replace(trimRightRegExp, '');
2100 
2101 					if (name && value.length > 0) {
2102 						// Opera will produce 700 instead of bold in their style values
2103 						if (name === 'font-weight' && value === '700')
2104 							value = 'bold';
2105 						else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2106 							value = value.toLowerCase();		
2107 
2108 						// Convert RGB colors to HEX
2109 						value = value.replace(rgbRegExp, toHex);
2110 
2111 						// Convert URLs and force them into url('value') format
2112 						value = value.replace(urlOrStrRegExp, processUrl);
2113 						styles[name] = isEncoded ? decode(value, true) : value;
2114 					}
2115 
2116 					styleRegExp.lastIndex = matches.index + matches[0].length;
2117 				}
2118 
2119 				// Compress the styles to reduce it's size for example IE will expand styles
2120 				compress("border", "");
2121 				compress("border", "-width");
2122 				compress("border", "-color");
2123 				compress("border", "-style");
2124 				compress("padding", "");
2125 				compress("margin", "");
2126 				compress2('border', 'border-width', 'border-style', 'border-color');
2127 
2128 				// Remove pointless border, IE produces these
2129 				if (styles.border === 'medium none')
2130 					delete styles.border;
2131 			}
2132 
2133 			return styles;
2134 		},
2135 
2136 		serialize : function(styles, element_name) {
2137 			var css = '', name, value;
2138 
2139 			function serializeStyles(name) {
2140 				var styleList, i, l, value;
2141 
2142 				styleList = schema.styles[name];
2143 				if (styleList) {
2144 					for (i = 0, l = styleList.length; i < l; i++) {
2145 						name = styleList[i];
2146 						value = styles[name];
2147 
2148 						if (value !== undef && value.length > 0)
2149 							css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2150 					}
2151 				}
2152 			};
2153 
2154 			// Serialize styles according to schema
2155 			if (element_name && schema && schema.styles) {
2156 				// Serialize global styles and element specific styles
2157 				serializeStyles('*');
2158 				serializeStyles(element_name);
2159 			} else {
2160 				// Output the styles in the order they are inside the object
2161 				for (name in styles) {
2162 					value = styles[name];
2163 
2164 					if (value !== undef && value.length > 0)
2165 						css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2166 				}
2167 			}
2168 
2169 			return css;
2170 		}
2171 	};
2172 };
2173 
2174 (function(tinymce) {
2175 	var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2176 
2177 	function split(str, delim) {
2178 		return str.split(delim || ',');
2179 	};
2180 
2181 	function unpack(lookup, data) {
2182 		var key, elements = {};
2183 
2184 		function replace(value) {
2185 			return value.replace(/[A-Z]+/g, function(key) {
2186 				return replace(lookup[key]);
2187 			});
2188 		};
2189 
2190 		// Unpack lookup
2191 		for (key in lookup) {
2192 			if (lookup.hasOwnProperty(key))
2193 				lookup[key] = replace(lookup[key]);
2194 		}
2195 
2196 		// Unpack and parse data into object map
2197 		replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2198 			attributes = split(attributes, '|');
2199 
2200 			elements[name] = {
2201 				attributes : makeMap(attributes),
2202 				attributesOrder : attributes,
2203 				children : makeMap(children, '|', {'#comment' : {}})
2204 			}
2205 		});
2206 
2207 		return elements;
2208 	};
2209 
2210 	function getHTML5() {
2211 		var html5 = mapCache.html5;
2212 
2213 		if (!html5) {
2214 			html5 = mapCache.html5 = unpack({
2215 					A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2216 					B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
2217 						'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2218 					C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
2219 						'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
2220 						'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2221 				}, 'html[A|manifest][body|head]' +
2222 					'head[A][base|command|link|meta|noscript|script|style|title]' +
2223 					'title[A][#]' +
2224 					'base[A|href|target][]' +
2225 					'link[A|href|rel|media|type|sizes][]' +
2226 					'meta[A|http-equiv|name|content|charset][]' +
2227 					'style[A|type|media|scoped][#]' +
2228 					'script[A|charset|type|src|defer|async][#]' +
2229 					'noscript[A][C]' +
2230 					'body[A][C]' +
2231 					'section[A][C]' +
2232 					'nav[A][C]' +
2233 					'article[A][C]' +
2234 					'aside[A][C]' +
2235 					'h1[A][B]' +
2236 					'h2[A][B]' +
2237 					'h3[A][B]' +
2238 					'h4[A][B]' +
2239 					'h5[A][B]' +
2240 					'h6[A][B]' +
2241 					'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2242 					'header[A][C]' +
2243 					'footer[A][C]' +
2244 					'address[A][C]' +
2245 					'p[A][B]' +
2246 					'br[A][]' +
2247 					'pre[A][B]' +
2248 					'dialog[A][dd|dt]' +
2249 					'blockquote[A|cite][C]' +
2250 					'ol[A|start|reversed][li]' +
2251 					'ul[A][li]' +
2252 					'li[A|value][C]' +
2253 					'dl[A][dd|dt]' +
2254 					'dt[A][B]' +
2255 					'dd[A][C]' +
2256 					'a[A|href|target|ping|rel|media|type][B]' +
2257 					'em[A][B]' +
2258 					'strong[A][B]' +
2259 					'small[A][B]' +
2260 					'cite[A][B]' +
2261 					'q[A|cite][B]' +
2262 					'dfn[A][B]' +
2263 					'abbr[A][B]' +
2264 					'code[A][B]' +
2265 					'var[A][B]' +
2266 					'samp[A][B]' +
2267 					'kbd[A][B]' +
2268 					'sub[A][B]' +
2269 					'sup[A][B]' +
2270 					'i[A][B]' +
2271 					'b[A][B]' +
2272 					'mark[A][B]' +
2273 					'progress[A|value|max][B]' +
2274 					'meter[A|value|min|max|low|high|optimum][B]' +
2275 					'time[A|datetime][B]' +
2276 					'ruby[A][B|rt|rp]' +
2277 					'rt[A][B]' +
2278 					'rp[A][B]' +
2279 					'bdo[A][B]' +
2280 					'span[A][B]' +
2281 					'ins[A|cite|datetime][B]' +
2282 					'del[A|cite|datetime][B]' +
2283 					'figure[A][C|legend|figcaption]' +
2284 					'figcaption[A][C]' +
2285 					'img[A|alt|src|height|width|usemap|ismap][]' +
2286 					'iframe[A|name|src|height|width|sandbox|seamless][]' +
2287 					'embed[A|src|height|width|type][]' +
2288 					'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2289 					'param[A|name|value][]' +
2290 					'details[A|open][C|legend]' +
2291 					'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2292 					'menu[A|type|label][C|li]' +
2293 					'legend[A][C|B]' +
2294 					'div[A][C]' +
2295 					'source[A|src|type|media][]' +
2296 					'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2297 					'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2298 					'hr[A][]' +
2299 					'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2300 					'fieldset[A|disabled|form|name][C|legend]' +
2301 					'label[A|form|for][B]' +
2302 					'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2303 						'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2304 					'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2305 					'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2306 					'datalist[A][B|option]' +
2307 					'optgroup[A|disabled|label][option]' +
2308 					'option[A|disabled|selected|label|value][]' +
2309 					'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2310 					'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2311 					'output[A|for|form|name][B]' +
2312 					'canvas[A|width|height][]' +
2313 					'map[A|name][B|C]' +
2314 					'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2315 					'mathml[A][]' +
2316 					'svg[A][]' +
2317 					'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2318 					'caption[A][C]' +
2319 					'colgroup[A|span][col]' +
2320 					'col[A|span][]' +
2321 					'thead[A][tr]' +
2322 					'tfoot[A][tr]' +
2323 					'tbody[A][tr]' +
2324 					'tr[A][th|td]' +
2325 					'th[A|headers|rowspan|colspan|scope][B]' +
2326 					'td[A|headers|rowspan|colspan][C]' +
2327 					'wbr[A][]'
2328 			);
2329 		}
2330 
2331 		return html5;
2332 	};
2333 
2334 	function getHTML4() {
2335 		var html4 = mapCache.html4;
2336 
2337 		if (!html4) {
2338 			// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2339 			html4 = mapCache.html4 = unpack({
2340 				Z : 'H|K|N|O|P',
2341 				Y : 'X|form|R|Q',
2342 				ZG : 'E|span|width|align|char|charoff|valign',
2343 				X : 'p|T|div|U|W|isindex|fieldset|table',
2344 				ZF : 'E|align|char|charoff|valign',
2345 				W : 'pre|hr|blockquote|address|center|noframes',
2346 				ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2347 				ZD : '[E][S]',
2348 				U : 'ul|ol|dl|menu|dir',
2349 				ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2350 				T : 'h1|h2|h3|h4|h5|h6',
2351 				ZB : 'X|S|Q',
2352 				S : 'R|P',
2353 				ZA : 'a|G|J|M|O|P',
2354 				R : 'a|H|K|N|O',
2355 				Q : 'noscript|P',
2356 				P : 'ins|del|script',
2357 				O : 'input|select|textarea|label|button',
2358 				N : 'M|L',
2359 				M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2360 				L : 'sub|sup',
2361 				K : 'J|I',
2362 				J : 'tt|i|b|u|s|strike',
2363 				I : 'big|small|font|basefont',
2364 				H : 'G|F',
2365 				G : 'br|span|bdo',
2366 				F : 'object|applet|img|map|iframe',
2367 				E : 'A|B|C',
2368 				D : 'accesskey|tabindex|onfocus|onblur',
2369 				C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2370 				B : 'lang|xml:lang|dir',
2371 				A : 'id|class|style|title'
2372 			}, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2373 				'style[B|id|type|media|title|xml:space][]' + 
2374 				'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2375 				'param[id|name|value|valuetype|type][]' + 
2376 				'p[E|align][#|S]' + 
2377 				'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2378 				'br[A|clear][]' + 
2379 				'span[E][#|S]' + 
2380 				'bdo[A|C|B][#|S]' + 
2381 				'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2382 				'h1[E|align][#|S]' + 
2383 				'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2384 				'map[B|C|A|name][X|form|Q|area]' + 
2385 				'h2[E|align][#|S]' + 
2386 				'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2387 				'h3[E|align][#|S]' + 
2388 				'tt[E][#|S]' + 
2389 				'i[E][#|S]' + 
2390 				'b[E][#|S]' + 
2391 				'u[E][#|S]' + 
2392 				's[E][#|S]' + 
2393 				'strike[E][#|S]' + 
2394 				'big[E][#|S]' + 
2395 				'small[E][#|S]' + 
2396 				'font[A|B|size|color|face][#|S]' + 
2397 				'basefont[id|size|color|face][]' + 
2398 				'em[E][#|S]' + 
2399 				'strong[E][#|S]' + 
2400 				'dfn[E][#|S]' + 
2401 				'code[E][#|S]' + 
2402 				'q[E|cite][#|S]' + 
2403 				'samp[E][#|S]' + 
2404 				'kbd[E][#|S]' + 
2405 				'var[E][#|S]' + 
2406 				'cite[E][#|S]' + 
2407 				'abbr[E][#|S]' + 
2408 				'acronym[E][#|S]' + 
2409 				'sub[E][#|S]' + 
2410 				'sup[E][#|S]' + 
2411 				'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2412 				'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2413 				'optgroup[E|disabled|label][option]' + 
2414 				'option[E|selected|disabled|label|value][]' + 
2415 				'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2416 				'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2417 				'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2418 				'h4[E|align][#|S]' + 
2419 				'ins[E|cite|datetime][#|Y]' + 
2420 				'h5[E|align][#|S]' + 
2421 				'del[E|cite|datetime][#|Y]' + 
2422 				'h6[E|align][#|S]' + 
2423 				'div[E|align][#|Y]' + 
2424 				'ul[E|type|compact][li]' + 
2425 				'li[E|type|value][#|Y]' + 
2426 				'ol[E|type|compact|start][li]' + 
2427 				'dl[E|compact][dt|dd]' + 
2428 				'dt[E][#|S]' + 
2429 				'dd[E][#|Y]' + 
2430 				'menu[E|compact][li]' + 
2431 				'dir[E|compact][li]' + 
2432 				'pre[E|width|xml:space][#|ZA]' + 
2433 				'hr[E|align|noshade|size|width][]' + 
2434 				'blockquote[E|cite][#|Y]' + 
2435 				'address[E][#|S|p]' + 
2436 				'center[E][#|Y]' + 
2437 				'noframes[E][#|Y]' + 
2438 				'isindex[A|B|prompt][]' + 
2439 				'fieldset[E][#|legend|Y]' + 
2440 				'legend[E|accesskey|align][#|S]' + 
2441 				'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2442 				'caption[E|align][#|S]' + 
2443 				'col[ZG][]' + 
2444 				'colgroup[ZG][col]' + 
2445 				'thead[ZF][tr]' + 
2446 				'tr[ZF|bgcolor][th|td]' + 
2447 				'th[E|ZE][#|Y]' + 
2448 				'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2449 				'noscript[E][#|Y]' + 
2450 				'td[E|ZE][#|Y]' + 
2451 				'tfoot[ZF][tr]' + 
2452 				'tbody[ZF][tr]' + 
2453 				'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2454 				'base[id|href|target][]' + 
2455 				'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2456 			);
2457 		}
2458 
2459 		return html4;
2460 	};
2461 
2462 	tinymce.html.Schema = function(settings) {
2463 		var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2464 		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2465 
2466 		// Creates an lookup table map object for the specified option or the default value
2467 		function createLookupTable(option, default_value, extend) {
2468 			var value = settings[option];
2469 
2470 			if (!value) {
2471 				// Get cached default map or make it if needed
2472 				value = mapCache[option];
2473 
2474 				if (!value) {
2475 					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2476 					value = tinymce.extend(value, extend);
2477 
2478 					mapCache[option] = value;
2479 				}
2480 			} else {
2481 				// Create custom map
2482 				value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2483 			}
2484 
2485 			return value;
2486 		};
2487 
2488 		settings = settings || {};
2489 		schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2490 
2491 		// Allow all elements and attributes if verify_html is set to false
2492 		if (settings.verify_html === false)
2493 			settings.valid_elements = '*[*]';
2494 
2495 		// Build styles list
2496 		if (settings.valid_styles) {
2497 			validStyles = {};
2498 
2499 			// Convert styles into a rule list
2500 			each(settings.valid_styles, function(value, key) {
2501 				validStyles[key] = tinymce.explode(value);
2502 			});
2503 		}
2504 
2505 		// Setup map objects
2506 		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
2507 		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2508 		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2509 		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2510 		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
2511 		blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 
2512 						'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 
2513 						'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup');
2514 
2515 		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2516 		function patternToRegExp(str) {
2517 			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2518 		};
2519 
2520 		// Parses the specified valid_elements string and adds to the current rules
2521 		// This function is a bit hard to read since it's heavily optimized for speed
2522 		function addValidElements(valid_elements) {
2523 			var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2524 				prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2525 				elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2526 				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2527 				hasPatternsRegExp = /[*?+]/;
2528 
2529 			if (valid_elements) {
2530 				// Split valid elements into an array with rules
2531 				valid_elements = split(valid_elements);
2532 
2533 				if (elements['@']) {
2534 					globalAttributes = elements['@'].attributes;
2535 					globalAttributesOrder = elements['@'].attributesOrder;
2536 				}
2537 
2538 				// Loop all rules
2539 				for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2540 					// Parse element rule
2541 					matches = elementRuleRegExp.exec(valid_elements[ei]);
2542 					if (matches) {
2543 						// Setup local names for matches
2544 						prefix = matches[1];
2545 						elementName = matches[2];
2546 						outputName = matches[3];
2547 						attrData = matches[4];
2548 
2549 						// Create new attributes and attributesOrder
2550 						attributes = {};
2551 						attributesOrder = [];
2552 
2553 						// Create the new element
2554 						element = {
2555 							attributes : attributes,
2556 							attributesOrder : attributesOrder
2557 						};
2558 
2559 						// Padd empty elements prefix
2560 						if (prefix === '#')
2561 							element.paddEmpty = true;
2562 
2563 						// Remove empty elements prefix
2564 						if (prefix === '-')
2565 							element.removeEmpty = true;
2566 
2567 						// Copy attributes from global rule into current rule
2568 						if (globalAttributes) {
2569 							for (key in globalAttributes)
2570 								attributes[key] = globalAttributes[key];
2571 
2572 							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2573 						}
2574 
2575 						// Attributes defined
2576 						if (attrData) {
2577 							attrData = split(attrData, '|');
2578 							for (ai = 0, al = attrData.length; ai < al; ai++) {
2579 								matches = attrRuleRegExp.exec(attrData[ai]);
2580 								if (matches) {
2581 									attr = {};
2582 									attrType = matches[1];
2583 									attrName = matches[2].replace(/::/g, ':');
2584 									prefix = matches[3];
2585 									value = matches[4];
2586 
2587 									// Required
2588 									if (attrType === '!') {
2589 										element.attributesRequired = element.attributesRequired || [];
2590 										element.attributesRequired.push(attrName);
2591 										attr.required = true;
2592 									}
2593 
2594 									// Denied from global
2595 									if (attrType === '-') {
2596 										delete attributes[attrName];
2597 										attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2598 										continue;
2599 									}
2600 
2601 									// Default value
2602 									if (prefix) {
2603 										// Default value
2604 										if (prefix === '=') {
2605 											element.attributesDefault = element.attributesDefault || [];
2606 											element.attributesDefault.push({name: attrName, value: value});
2607 											attr.defaultValue = value;
2608 										}
2609 
2610 										// Forced value
2611 										if (prefix === ':') {
2612 											element.attributesForced = element.attributesForced || [];
2613 											element.attributesForced.push({name: attrName, value: value});
2614 											attr.forcedValue = value;
2615 										}
2616 
2617 										// Required values
2618 										if (prefix === '<')
2619 											attr.validValues = makeMap(value, '?');
2620 									}
2621 
2622 									// Check for attribute patterns
2623 									if (hasPatternsRegExp.test(attrName)) {
2624 										element.attributePatterns = element.attributePatterns || [];
2625 										attr.pattern = patternToRegExp(attrName);
2626 										element.attributePatterns.push(attr);
2627 									} else {
2628 										// Add attribute to order list if it doesn't already exist
2629 										if (!attributes[attrName])
2630 											attributesOrder.push(attrName);
2631 
2632 										attributes[attrName] = attr;
2633 									}
2634 								}
2635 							}
2636 						}
2637 
2638 						// Global rule, store away these for later usage
2639 						if (!globalAttributes && elementName == '@') {
2640 							globalAttributes = attributes;
2641 							globalAttributesOrder = attributesOrder;
2642 						}
2643 
2644 						// Handle substitute elements such as b/strong
2645 						if (outputName) {
2646 							element.outputName = elementName;
2647 							elements[outputName] = element;
2648 						}
2649 
2650 						// Add pattern or exact element
2651 						if (hasPatternsRegExp.test(elementName)) {
2652 							element.pattern = patternToRegExp(elementName);
2653 							patternElements.push(element);
2654 						} else
2655 							elements[elementName] = element;
2656 					}
2657 				}
2658 			}
2659 		};
2660 
2661 		function setValidElements(valid_elements) {
2662 			elements = {};
2663 			patternElements = [];
2664 
2665 			addValidElements(valid_elements);
2666 
2667 			each(schemaItems, function(element, name) {
2668 				children[name] = element.children;
2669 			});
2670 		};
2671 
2672 		// Adds custom non HTML elements to the schema
2673 		function addCustomElements(custom_elements) {
2674 			var customElementRegExp = /^(~)?(.+)$/;
2675 
2676 			if (custom_elements) {
2677 				each(split(custom_elements), function(rule) {
2678 					var matches = customElementRegExp.exec(rule),
2679 						inline = matches[1] === '~',
2680 						cloneName = inline ? 'span' : 'div',
2681 						name = matches[2];
2682 
2683 					children[name] = children[cloneName];
2684 					customElementsMap[name] = cloneName;
2685 
2686 					// If it's not marked as inline then add it to valid block elements
2687 					if (!inline)
2688 						blockElementsMap[name] = {};
2689 
2690 					// Add custom elements at span/div positions
2691 					each(children, function(element, child) {
2692 						if (element[cloneName])
2693 							element[name] = element[cloneName];
2694 					});
2695 				});
2696 			}
2697 		};
2698 
2699 		// Adds valid children to the schema object
2700 		function addValidChildren(valid_children) {
2701 			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2702 
2703 			if (valid_children) {
2704 				each(split(valid_children), function(rule) {
2705 					var matches = childRuleRegExp.exec(rule), parent, prefix;
2706 
2707 					if (matches) {
2708 						prefix = matches[1];
2709 
2710 						// Add/remove items from default
2711 						if (prefix)
2712 							parent = children[matches[2]];
2713 						else
2714 							parent = children[matches[2]] = {'#comment' : {}};
2715 
2716 						parent = children[matches[2]];
2717 
2718 						each(split(matches[3], '|'), function(child) {
2719 							if (prefix === '-')
2720 								delete parent[child];
2721 							else
2722 								parent[child] = {};
2723 						});
2724 					}
2725 				});
2726 			}
2727 		};
2728 
2729 		function getElementRule(name) {
2730 			var element = elements[name], i;
2731 
2732 			// Exact match found
2733 			if (element)
2734 				return element;
2735 
2736 			// No exact match then try the patterns
2737 			i = patternElements.length;
2738 			while (i--) {
2739 				element = patternElements[i];
2740 
2741 				if (element.pattern.test(name))
2742 					return element;
2743 			}
2744 		};
2745 
2746 		if (!settings.valid_elements) {
2747 			// No valid elements defined then clone the elements from the schema spec
2748 			each(schemaItems, function(element, name) {
2749 				elements[name] = {
2750 					attributes : element.attributes,
2751 					attributesOrder : element.attributesOrder
2752 				};
2753 
2754 				children[name] = element.children;
2755 			});
2756 
2757 			// Switch these on HTML4
2758 			if (settings.schema != "html5") {
2759 				each(split('strong/b,em/i'), function(item) {
2760 					item = split(item, '/');
2761 					elements[item[1]].outputName = item[0];
2762 				});
2763 			}
2764 
2765 			// Add default alt attribute for images
2766 			elements.img.attributesDefault = [{name: 'alt', value: ''}];
2767 
2768 			// Remove these if they are empty by default
2769 			each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
2770 				if (elements[name]) {
2771 					elements[name].removeEmpty = true;
2772 				}
2773 			});
2774 
2775 			// Padd these by default
2776 			each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
2777 				elements[name].paddEmpty = true;
2778 			});
2779 		} else
2780 			setValidElements(settings.valid_elements);
2781 
2782 		addCustomElements(settings.custom_elements);
2783 		addValidChildren(settings.valid_children);
2784 		addValidElements(settings.extended_valid_elements);
2785 
2786 		// Todo: Remove this when we fix list handling to be valid
2787 		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
2788 
2789 		// Delete invalid elements
2790 		if (settings.invalid_elements) {
2791 			tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
2792 				if (elements[item])
2793 					delete elements[item];
2794 			});
2795 		}
2796 
2797 		// If the user didn't allow span only allow internal spans
2798 		if (!getElementRule('span'))
2799 			addValidElements('span[!data-mce-type|*]');
2800 
2801 		self.children = children;
2802 
2803 		self.styles = validStyles;
2804 
2805 		self.getBoolAttrs = function() {
2806 			return boolAttrMap;
2807 		};
2808 
2809 		self.getBlockElements = function() {
2810 			return blockElementsMap;
2811 		};
2812 
2813 		self.getShortEndedElements = function() {
2814 			return shortEndedElementsMap;
2815 		};
2816 
2817 		self.getSelfClosingElements = function() {
2818 			return selfClosingElementsMap;
2819 		};
2820 
2821 		self.getNonEmptyElements = function() {
2822 			return nonEmptyElementsMap;
2823 		};
2824 
2825 		self.getWhiteSpaceElements = function() {
2826 			return whiteSpaceElementsMap;
2827 		};
2828 
2829 		self.isValidChild = function(name, child) {
2830 			var parent = children[name];
2831 
2832 			return !!(parent && parent[child]);
2833 		};
2834 
2835 		self.isValid = function(name, attr) {
2836 			var attrPatterns, i, rule = getElementRule(name);
2837 
2838 			// Check if it's a valid element
2839 			if (rule) {
2840 				if (attr) {
2841 					// Check if attribute name exists
2842 					if (rule.attributes[attr]) {
2843 						return true;
2844 					}
2845 
2846 					// Check if attribute matches a regexp pattern
2847 					attrPatterns = rule.attributePatterns;
2848 					if (attrPatterns) {
2849 						i = attrPatterns.length;
2850 						while (i--) {
2851 							if (attrPatterns[i].pattern.test(name)) {
2852 								return true;
2853 							}
2854 						}
2855 					}
2856 				} else {
2857 					return true;
2858 				}
2859 			}
2860 
2861 			// No match
2862 			return false;
2863 		};
2864 		
2865 		self.getElementRule = getElementRule;
2866 
2867 		self.getCustomElements = function() {
2868 			return customElementsMap;
2869 		};
2870 
2871 		self.addValidElements = addValidElements;
2872 
2873 		self.setValidElements = setValidElements;
2874 
2875 		self.addCustomElements = addCustomElements;
2876 
2877 		self.addValidChildren = addValidChildren;
2878 	};
2879 })(tinymce);
2880 
2881 (function(tinymce) {
2882 	tinymce.html.SaxParser = function(settings, schema) {
2883 		var self = this, noop = function() {};
2884 
2885 		settings = settings || {};
2886 		self.schema = schema = schema || new tinymce.html.Schema();
2887 
2888 		if (settings.fix_self_closing !== false)
2889 			settings.fix_self_closing = true;
2890 
2891 		// Add handler functions from settings and setup default handlers
2892 		tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2893 			if (name)
2894 				self[name] = settings[name] || noop;
2895 		});
2896 
2897 		self.parse = function(html) {
2898 			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2899 				shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
2900 				validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2901 				tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
2902 
2903 			function processEndTag(name) {
2904 				var pos, i;
2905 
2906 				// Find position of parent of the same type
2907 				pos = stack.length;
2908 				while (pos--) {
2909 					if (stack[pos].name === name)
2910 						break;						
2911 				}
2912 
2913 				// Found parent
2914 				if (pos >= 0) {
2915 					// Close all the open elements
2916 					for (i = stack.length - 1; i >= pos; i--) {
2917 						name = stack[i];
2918 
2919 						if (name.valid)
2920 							self.end(name.name);
2921 					}
2922 
2923 					// Remove the open elements from the stack
2924 					stack.length = pos;
2925 				}
2926 			};
2927 
2928 			function parseAttribute(match, name, value, val2, val3) {
2929 				var attrRule, i;
2930 
2931 				name = name.toLowerCase();
2932 				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2933 
2934 				// Validate name and value
2935 				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
2936 					attrRule = validAttributesMap[name];
2937 
2938 					// Find rule by pattern matching
2939 					if (!attrRule && validAttributePatterns) {
2940 						i = validAttributePatterns.length;
2941 						while (i--) {
2942 							attrRule = validAttributePatterns[i];
2943 							if (attrRule.pattern.test(name))
2944 								break;
2945 						}
2946 
2947 						// No rule matched
2948 						if (i === -1)
2949 							attrRule = null;
2950 					}
2951 
2952 					// No attribute rule found
2953 					if (!attrRule)
2954 						return;
2955 
2956 					// Validate value
2957 					if (attrRule.validValues && !(value in attrRule.validValues))
2958 						return;
2959 				}
2960 
2961 				// Add attribute to list and map
2962 				attrList.map[name] = value;
2963 				attrList.push({
2964 					name: name,
2965 					value: value
2966 				});
2967 			};
2968 
2969 			// Precompile RegExps and map objects
2970 			tokenRegExp = new RegExp('<(?:' +
2971 				'(?:!--([\\w\\W]*?)-->)|' + // Comment
2972 				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2973 				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2974 				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
2975 				'(?:\\/([^>]+)>)|' + // End element
2976 				'(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
2977 			')', 'g');
2978 
2979 			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2980 			specialElements = {
2981 				'script' : /<\/script[^>]*>/gi,
2982 				'style' : /<\/style[^>]*>/gi,
2983 				'noscript' : /<\/noscript[^>]*>/gi
2984 			};
2985 
2986 			// Setup lookup tables for empty elements and boolean attributes
2987 			shortEndedElements = schema.getShortEndedElements();
2988 			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
2989 			fillAttrsMap = schema.getBoolAttrs();
2990 			validate = settings.validate;
2991 			removeInternalElements = settings.remove_internals;
2992 			fixSelfClosing = settings.fix_self_closing;
2993 			isIE = tinymce.isIE;
2994 			invalidPrefixRegExp = /^:/;
2995 
2996 			while (matches = tokenRegExp.exec(html)) {
2997 				// Text
2998 				if (index < matches.index)
2999 					self.text(decode(html.substr(index, matches.index - index)));
3000 
3001 				if (value = matches[6]) { // End element
3002 					value = value.toLowerCase();
3003 
3004 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3005 					if (isIE && invalidPrefixRegExp.test(value))
3006 						value = value.substr(1);
3007 
3008 					processEndTag(value);
3009 				} else if (value = matches[7]) { // Start element
3010 					value = value.toLowerCase();
3011 
3012 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3013 					if (isIE && invalidPrefixRegExp.test(value))
3014 						value = value.substr(1);
3015 
3016 					isShortEnded = value in shortEndedElements;
3017 
3018 					// Is self closing tag for example an <li> after an open <li>
3019 					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3020 						processEndTag(value);
3021 
3022 					// Validate element
3023 					if (!validate || (elementRule = schema.getElementRule(value))) {
3024 						isValidElement = true;
3025 
3026 						// Grab attributes map and patters when validation is enabled
3027 						if (validate) {
3028 							validAttributesMap = elementRule.attributes;
3029 							validAttributePatterns = elementRule.attributePatterns;
3030 						}
3031 
3032 						// Parse attributes
3033 						if (attribsValue = matches[8]) {
3034 							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3035 
3036 							// If the element has internal attributes then remove it if we are told to do so
3037 							if (isInternalElement && removeInternalElements)
3038 								isValidElement = false;
3039 
3040 							attrList = [];
3041 							attrList.map = {};
3042 
3043 							attribsValue.replace(attrRegExp, parseAttribute);
3044 						} else {
3045 							attrList = [];
3046 							attrList.map = {};
3047 						}
3048 
3049 						// Process attributes if validation is enabled
3050 						if (validate && !isInternalElement) {
3051 							attributesRequired = elementRule.attributesRequired;
3052 							attributesDefault = elementRule.attributesDefault;
3053 							attributesForced = elementRule.attributesForced;
3054 
3055 							// Handle forced attributes
3056 							if (attributesForced) {
3057 								i = attributesForced.length;
3058 								while (i--) {
3059 									attr = attributesForced[i];
3060 									name = attr.name;
3061 									attrValue = attr.value;
3062 
3063 									if (attrValue === '{$uid}')
3064 										attrValue = 'mce_' + idCount++;
3065 
3066 									attrList.map[name] = attrValue;
3067 									attrList.push({name: name, value: attrValue});
3068 								}
3069 							}
3070 
3071 							// Handle default attributes
3072 							if (attributesDefault) {
3073 								i = attributesDefault.length;
3074 								while (i--) {
3075 									attr = attributesDefault[i];
3076 									name = attr.name;
3077 
3078 									if (!(name in attrList.map)) {
3079 										attrValue = attr.value;
3080 
3081 										if (attrValue === '{$uid}')
3082 											attrValue = 'mce_' + idCount++;
3083 
3084 										attrList.map[name] = attrValue;
3085 										attrList.push({name: name, value: attrValue});
3086 									}
3087 								}
3088 							}
3089 
3090 							// Handle required attributes
3091 							if (attributesRequired) {
3092 								i = attributesRequired.length;
3093 								while (i--) {
3094 									if (attributesRequired[i] in attrList.map)
3095 										break;
3096 								}
3097 
3098 								// None of the required attributes where found
3099 								if (i === -1)
3100 									isValidElement = false;
3101 							}
3102 
3103 							// Invalidate element if it's marked as bogus
3104 							if (attrList.map['data-mce-bogus'])
3105 								isValidElement = false;
3106 						}
3107 
3108 						if (isValidElement)
3109 							self.start(value, attrList, isShortEnded);
3110 					} else
3111 						isValidElement = false;
3112 
3113 					// Treat script, noscript and style a bit different since they may include code that looks like elements
3114 					if (endRegExp = specialElements[value]) {
3115 						endRegExp.lastIndex = index = matches.index + matches[0].length;
3116 
3117 						if (matches = endRegExp.exec(html)) {
3118 							if (isValidElement)
3119 								text = html.substr(index, matches.index - index);
3120 
3121 							index = matches.index + matches[0].length;
3122 						} else {
3123 							text = html.substr(index);
3124 							index = html.length;
3125 						}
3126 
3127 						if (isValidElement && text.length > 0)
3128 							self.text(text, true);
3129 
3130 						if (isValidElement)
3131 							self.end(value);
3132 
3133 						tokenRegExp.lastIndex = index;
3134 						continue;
3135 					}
3136 
3137 					// Push value on to stack
3138 					if (!isShortEnded) {
3139 						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3140 							stack.push({name: value, valid: isValidElement});
3141 						else if (isValidElement)
3142 							self.end(value);
3143 					}
3144 				} else if (value = matches[1]) { // Comment
3145 					self.comment(value);
3146 				} else if (value = matches[2]) { // CDATA
3147 					self.cdata(value);
3148 				} else if (value = matches[3]) { // DOCTYPE
3149 					self.doctype(value);
3150 				} else if (value = matches[4]) { // PI
3151 					self.pi(value, matches[5]);
3152 				}
3153 
3154 				index = matches.index + matches[0].length;
3155 			}
3156 
3157 			// Text
3158 			if (index < html.length)
3159 				self.text(decode(html.substr(index)));
3160 
3161 			// Close any open elements
3162 			for (i = stack.length - 1; i >= 0; i--) {
3163 				value = stack[i];
3164 
3165 				if (value.valid)
3166 					self.end(value.name);
3167 			}
3168 		};
3169 	}
3170 })(tinymce);
3171 
3172 (function(tinymce) {
3173 	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3174 		'#text' : 3,
3175 		'#comment' : 8,
3176 		'#cdata' : 4,
3177 		'#pi' : 7,
3178 		'#doctype' : 10,
3179 		'#document-fragment' : 11
3180 	};
3181 
3182 	// Walks the tree left/right
3183 	function walk(node, root_node, prev) {
3184 		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3185 
3186 		// Walk into nodes if it has a start
3187 		if (node[startName])
3188 			return node[startName];
3189 
3190 		// Return the sibling if it has one
3191 		if (node !== root_node) {
3192 			sibling = node[siblingName];
3193 
3194 			if (sibling)
3195 				return sibling;
3196 
3197 			// Walk up the parents to look for siblings
3198 			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3199 				sibling = parent[siblingName];
3200 
3201 				if (sibling)
3202 					return sibling;
3203 			}
3204 		}
3205 	};
3206 
3207 	function Node(name, type) {
3208 		this.name = name;
3209 		this.type = type;
3210 
3211 		if (type === 1) {
3212 			this.attributes = [];
3213 			this.attributes.map = {};
3214 		}
3215 	}
3216 
3217 	tinymce.extend(Node.prototype, {
3218 		replace : function(node) {
3219 			var self = this;
3220 
3221 			if (node.parent)
3222 				node.remove();
3223 
3224 			self.insert(node, self);
3225 			self.remove();
3226 
3227 			return self;
3228 		},
3229 
3230 		attr : function(name, value) {
3231 			var self = this, attrs, i, undef;
3232 
3233 			if (typeof name !== "string") {
3234 				for (i in name)
3235 					self.attr(i, name[i]);
3236 
3237 				return self;
3238 			}
3239 
3240 			if (attrs = self.attributes) {
3241 				if (value !== undef) {
3242 					// Remove attribute
3243 					if (value === null) {
3244 						if (name in attrs.map) {
3245 							delete attrs.map[name];
3246 
3247 							i = attrs.length;
3248 							while (i--) {
3249 								if (attrs[i].name === name) {
3250 									attrs = attrs.splice(i, 1);
3251 									return self;
3252 								}
3253 							}
3254 						}
3255 
3256 						return self;
3257 					}
3258 
3259 					// Set attribute
3260 					if (name in attrs.map) {
3261 						// Set attribute
3262 						i = attrs.length;
3263 						while (i--) {
3264 							if (attrs[i].name === name) {
3265 								attrs[i].value = value;
3266 								break;
3267 							}
3268 						}
3269 					} else
3270 						attrs.push({name: name, value: value});
3271 
3272 					attrs.map[name] = value;
3273 
3274 					return self;
3275 				} else {
3276 					return attrs.map[name];
3277 				}
3278 			}
3279 		},
3280 
3281 		clone : function() {
3282 			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3283 
3284 			// Clone element attributes
3285 			if (selfAttrs = self.attributes) {
3286 				cloneAttrs = [];
3287 				cloneAttrs.map = {};
3288 
3289 				for (i = 0, l = selfAttrs.length; i < l; i++) {
3290 					selfAttr = selfAttrs[i];
3291 
3292 					// Clone everything except id
3293 					if (selfAttr.name !== 'id') {
3294 						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3295 						cloneAttrs.map[selfAttr.name] = selfAttr.value;
3296 					}
3297 				}
3298 
3299 				clone.attributes = cloneAttrs;
3300 			}
3301 
3302 			clone.value = self.value;
3303 			clone.shortEnded = self.shortEnded;
3304 
3305 			return clone;
3306 		},
3307 
3308 		wrap : function(wrapper) {
3309 			var self = this;
3310 
3311 			self.parent.insert(wrapper, self);
3312 			wrapper.append(self);
3313 
3314 			return self;
3315 		},
3316 
3317 		unwrap : function() {
3318 			var self = this, node, next;
3319 
3320 			for (node = self.firstChild; node; ) {
3321 				next = node.next;
3322 				self.insert(node, self, true);
3323 				node = next;
3324 			}
3325 
3326 			self.remove();
3327 		},
3328 
3329 		remove : function() {
3330 			var self = this, parent = self.parent, next = self.next, prev = self.prev;
3331 
3332 			if (parent) {
3333 				if (parent.firstChild === self) {
3334 					parent.firstChild = next;
3335 
3336 					if (next)
3337 						next.prev = null;
3338 				} else {
3339 					prev.next = next;
3340 				}
3341 
3342 				if (parent.lastChild === self) {
3343 					parent.lastChild = prev;
3344 
3345 					if (prev)
3346 						prev.next = null;
3347 				} else {
3348 					next.prev = prev;
3349 				}
3350 
3351 				self.parent = self.next = self.prev = null;
3352 			}
3353 
3354 			return self;
3355 		},
3356 
3357 		append : function(node) {
3358 			var self = this, last;
3359 
3360 			if (node.parent)
3361 				node.remove();
3362 
3363 			last = self.lastChild;
3364 			if (last) {
3365 				last.next = node;
3366 				node.prev = last;
3367 				self.lastChild = node;
3368 			} else
3369 				self.lastChild = self.firstChild = node;
3370 
3371 			node.parent = self;
3372 
3373 			return node;
3374 		},
3375 
3376 		insert : function(node, ref_node, before) {
3377 			var parent;
3378 
3379 			if (node.parent)
3380 				node.remove();
3381 
3382 			parent = ref_node.parent || this;
3383 
3384 			if (before) {
3385 				if (ref_node === parent.firstChild)
3386 					parent.firstChild = node;
3387 				else
3388 					ref_node.prev.next = node;
3389 
3390 				node.prev = ref_node.prev;
3391 				node.next = ref_node;
3392 				ref_node.prev = node;
3393 			} else {
3394 				if (ref_node === parent.lastChild)
3395 					parent.lastChild = node;
3396 				else
3397 					ref_node.next.prev = node;
3398 
3399 				node.next = ref_node.next;
3400 				node.prev = ref_node;
3401 				ref_node.next = node;
3402 			}
3403 
3404 			node.parent = parent;
3405 
3406 			return node;
3407 		},
3408 
3409 		getAll : function(name) {
3410 			var self = this, node, collection = [];
3411 
3412 			for (node = self.firstChild; node; node = walk(node, self)) {
3413 				if (node.name === name)
3414 					collection.push(node);
3415 			}
3416 
3417 			return collection;
3418 		},
3419 
3420 		empty : function() {
3421 			var self = this, nodes, i, node;
3422 
3423 			// Remove all children
3424 			if (self.firstChild) {
3425 				nodes = [];
3426 
3427 				// Collect the children
3428 				for (node = self.firstChild; node; node = walk(node, self))
3429 					nodes.push(node);
3430 
3431 				// Remove the children
3432 				i = nodes.length;
3433 				while (i--) {
3434 					node = nodes[i];
3435 					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3436 				}
3437 			}
3438 
3439 			self.firstChild = self.lastChild = null;
3440 
3441 			return self;
3442 		},
3443 
3444 		isEmpty : function(elements) {
3445 			var self = this, node = self.firstChild, i, name;
3446 
3447 			if (node) {
3448 				do {
3449 					if (node.type === 1) {
3450 						// Ignore bogus elements
3451 						if (node.attributes.map['data-mce-bogus'])
3452 							continue;
3453 
3454 						// Keep empty elements like <img />
3455 						if (elements[node.name])
3456 							return false;
3457 
3458 						// Keep elements with data attributes or name attribute like <a name="1"></a>
3459 						i = node.attributes.length;
3460 						while (i--) {
3461 							name = node.attributes[i].name;
3462 							if (name === "name" || name.indexOf('data-') === 0)
3463 								return false;
3464 						}
3465 					}
3466 
3467 					// Keep comments
3468 					if (node.type === 8)
3469 						return false;
3470 					
3471 					// Keep non whitespace text nodes
3472 					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3473 						return false;
3474 				} while (node = walk(node, self));
3475 			}
3476 
3477 			return true;
3478 		},
3479 
3480 		walk : function(prev) {
3481 			return walk(this, null, prev);
3482 		}
3483 	});
3484 
3485 	tinymce.extend(Node, {
3486 		create : function(name, attrs) {
3487 			var node, attrName;
3488 
3489 			// Create node
3490 			node = new Node(name, typeLookup[name] || 1);
3491 
3492 			// Add attributes if needed
3493 			if (attrs) {
3494 				for (attrName in attrs)
3495 					node.attr(attrName, attrs[attrName]);
3496 			}
3497 
3498 			return node;
3499 		}
3500 	});
3501 
3502 	tinymce.html.Node = Node;
3503 })(tinymce);
3504 
3505 (function(tinymce) {
3506 	var Node = tinymce.html.Node;
3507 
3508 	tinymce.html.DomParser = function(settings, schema) {
3509 		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3510 
3511 		settings = settings || {};
3512 		settings.validate = "validate" in settings ? settings.validate : true;
3513 		settings.root_name = settings.root_name || 'body';
3514 		self.schema = schema = schema || new tinymce.html.Schema();
3515 
3516 		function fixInvalidChildren(nodes) {
3517 			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3518 				childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
3519 
3520 			nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3521 			nonEmptyElements = schema.getNonEmptyElements();
3522 
3523 			for (ni = 0; ni < nodes.length; ni++) {
3524 				node = nodes[ni];
3525 
3526 				// Already removed
3527 				if (!node.parent)
3528 					continue;
3529 
3530 				// Get list of all parent nodes until we find a valid parent to stick the child into
3531 				parents = [node];
3532 				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3533 					parents.push(parent);
3534 
3535 				// Found a suitable parent
3536 				if (parent && parents.length > 1) {
3537 					// Reverse the array since it makes looping easier
3538 					parents.reverse();
3539 
3540 					// Clone the related parent and insert that after the moved node
3541 					newParent = currentNode = self.filterNode(parents[0].clone());
3542 
3543 					// Start cloning and moving children on the left side of the target node
3544 					for (i = 0; i < parents.length - 1; i++) {
3545 						if (schema.isValidChild(currentNode.name, parents[i].name)) {
3546 							tempNode = self.filterNode(parents[i].clone());
3547 							currentNode.append(tempNode);
3548 						} else
3549 							tempNode = currentNode;
3550 
3551 						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3552 							nextNode = childNode.next;
3553 							tempNode.append(childNode);
3554 							childNode = nextNode;
3555 						}
3556 
3557 						currentNode = tempNode;
3558 					}
3559 
3560 					if (!newParent.isEmpty(nonEmptyElements)) {
3561 						parent.insert(newParent, parents[0], true);
3562 						parent.insert(node, newParent);
3563 					} else {
3564 						parent.insert(node, parents[0], true);
3565 					}
3566 
3567 					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3568 					parent = parents[0];
3569 					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3570 						parent.empty().remove();
3571 					}
3572 				} else if (node.parent) {
3573 					// If it's an LI try to find a UL/OL for it or wrap it
3574 					if (node.name === 'li') {
3575 						sibling = node.prev;
3576 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3577 							sibling.append(node);
3578 							continue;
3579 						}
3580 
3581 						sibling = node.next;
3582 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3583 							sibling.insert(node, sibling.firstChild, true);
3584 							continue;
3585 						}
3586 
3587 						node.wrap(self.filterNode(new Node('ul', 1)));
3588 						continue;
3589 					}
3590 
3591 					// Try wrapping the element in a DIV
3592 					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3593 						node.wrap(self.filterNode(new Node('div', 1)));
3594 					} else {
3595 						// We failed wrapping it, then remove or unwrap it
3596 						if (node.name === 'style' || node.name === 'script')
3597 							node.empty().remove();
3598 						else
3599 							node.unwrap();
3600 					}
3601 				}
3602 			}
3603 		};
3604 
3605 		self.filterNode = function(node) {
3606 			var i, name, list;
3607 
3608 			// Run element filters
3609 			if (name in nodeFilters) {
3610 				list = matchedNodes[name];
3611 
3612 				if (list)
3613 					list.push(node);
3614 				else
3615 					matchedNodes[name] = [node];
3616 			}
3617 
3618 			// Run attribute filters
3619 			i = attributeFilters.length;
3620 			while (i--) {
3621 				name = attributeFilters[i].name;
3622 
3623 				if (name in node.attributes.map) {
3624 					list = matchedAttributes[name];
3625 
3626 					if (list)
3627 						list.push(node);
3628 					else
3629 						matchedAttributes[name] = [node];
3630 				}
3631 			}
3632 
3633 			return node;
3634 		};
3635 
3636 		self.addNodeFilter = function(name, callback) {
3637 			tinymce.each(tinymce.explode(name), function(name) {
3638 				var list = nodeFilters[name];
3639 
3640 				if (!list)
3641 					nodeFilters[name] = list = [];
3642 
3643 				list.push(callback);
3644 			});
3645 		};
3646 
3647 		self.addAttributeFilter = function(name, callback) {
3648 			tinymce.each(tinymce.explode(name), function(name) {
3649 				var i;
3650 
3651 				for (i = 0; i < attributeFilters.length; i++) {
3652 					if (attributeFilters[i].name === name) {
3653 						attributeFilters[i].callbacks.push(callback);
3654 						return;
3655 					}
3656 				}
3657 
3658 				attributeFilters.push({name: name, callbacks: [callback]});
3659 			});
3660 		};
3661 
3662 		self.parse = function(html, args) {
3663 			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3664 				blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3665 				endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3666 
3667 			args = args || {};
3668 			matchedNodes = {};
3669 			matchedAttributes = {};
3670 			blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3671 			nonEmptyElements = schema.getNonEmptyElements();
3672 			children = schema.children;
3673 			validate = settings.validate;
3674 			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3675 
3676 			whiteSpaceElements = schema.getWhiteSpaceElements();
3677 			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3678 			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3679 			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3680 			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3681 
3682 			function addRootBlocks() {
3683 				var node = rootNode.firstChild, next, rootBlockNode;
3684 
3685 				while (node) {
3686 					next = node.next;
3687 
3688 					if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3689 						if (!rootBlockNode) {
3690 							// Create a new root block element
3691 							rootBlockNode = createNode(rootBlockName, 1);
3692 							rootNode.insert(rootBlockNode, node);
3693 							rootBlockNode.append(node);
3694 						} else
3695 							rootBlockNode.append(node);
3696 					} else {
3697 						rootBlockNode = null;
3698 					}
3699 
3700 					node = next;
3701 				};
3702 			};
3703 
3704 			function createNode(name, type) {
3705 				var node = new Node(name, type), list;
3706 
3707 				if (name in nodeFilters) {
3708 					list = matchedNodes[name];
3709 
3710 					if (list)
3711 						list.push(node);
3712 					else
3713 						matchedNodes[name] = [node];
3714 				}
3715 
3716 				return node;
3717 			};
3718 
3719 			function removeWhitespaceBefore(node) {
3720 				var textNode, textVal, sibling;
3721 
3722 				for (textNode = node.prev; textNode && textNode.type === 3; ) {
3723 					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3724 
3725 					if (textVal.length > 0) {
3726 						textNode.value = textVal;
3727 						textNode = textNode.prev;
3728 					} else {
3729 						sibling = textNode.prev;
3730 						textNode.remove();
3731 						textNode = sibling;
3732 					}
3733 				}
3734 			};
3735 
3736 			function cloneAndExcludeBlocks(input) {
3737 				var name, output = {};
3738 
3739 				for (name in input) {
3740 					if (name !== 'li' && name != 'p') {
3741 						output[name] = input[name];
3742 					}
3743 				}
3744 
3745 				return output;
3746 			};
3747 
3748 			parser = new tinymce.html.SaxParser({
3749 				validate : validate,
3750 
3751 				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
3752 				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
3753 
3754 				cdata: function(text) {
3755 					node.append(createNode('#cdata', 4)).value = text;
3756 				},
3757 
3758 				text: function(text, raw) {
3759 					var textNode;
3760 
3761 					// Trim all redundant whitespace on non white space elements
3762 					if (!isInWhiteSpacePreservedElement) {
3763 						text = text.replace(allWhiteSpaceRegExp, ' ');
3764 
3765 						if (node.lastChild && blockElements[node.lastChild.name])
3766 							text = text.replace(startWhiteSpaceRegExp, '');
3767 					}
3768 
3769 					// Do we need to create the node
3770 					if (text.length !== 0) {
3771 						textNode = createNode('#text', 3);
3772 						textNode.raw = !!raw;
3773 						node.append(textNode).value = text;
3774 					}
3775 				},
3776 
3777 				comment: function(text) {
3778 					node.append(createNode('#comment', 8)).value = text;
3779 				},
3780 
3781 				pi: function(name, text) {
3782 					node.append(createNode(name, 7)).value = text;
3783 					removeWhitespaceBefore(node);
3784 				},
3785 
3786 				doctype: function(text) {
3787 					var newNode;
3788 		
3789 					newNode = node.append(createNode('#doctype', 10));
3790 					newNode.value = text;
3791 					removeWhitespaceBefore(node);
3792 				},
3793 
3794 				start: function(name, attrs, empty) {
3795 					var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
3796 
3797 					elementRule = validate ? schema.getElementRule(name) : {};
3798 					if (elementRule) {
3799 						newNode = createNode(elementRule.outputName || name, 1);
3800 						newNode.attributes = attrs;
3801 						newNode.shortEnded = empty;
3802 
3803 						node.append(newNode);
3804 
3805 						// Check if node is valid child of the parent node is the child is
3806 						// unknown we don't collect it since it's probably a custom element
3807 						parent = children[node.name];
3808 						if (parent && children[newNode.name] && !parent[newNode.name])
3809 							invalidChildren.push(newNode);
3810 
3811 						attrFiltersLen = attributeFilters.length;
3812 						while (attrFiltersLen--) {
3813 							attrName = attributeFilters[attrFiltersLen].name;
3814 
3815 							if (attrName in attrs.map) {
3816 								list = matchedAttributes[attrName];
3817 
3818 								if (list)
3819 									list.push(newNode);
3820 								else
3821 									matchedAttributes[attrName] = [newNode];
3822 							}
3823 						}
3824 
3825 						// Trim whitespace before block
3826 						if (blockElements[name])
3827 							removeWhitespaceBefore(newNode);
3828 
3829 						// Change current node if the element wasn't empty i.e not <br /> or <img />
3830 						if (!empty)
3831 							node = newNode;
3832 
3833 						// Check if we are inside a whitespace preserved element
3834 						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3835 							isInWhiteSpacePreservedElement = true;
3836 						}
3837 					}
3838 				},
3839 
3840 				end: function(name) {
3841 					var textNode, elementRule, text, sibling, tempNode;
3842 
3843 					elementRule = validate ? schema.getElementRule(name) : {};
3844 					if (elementRule) {
3845 						if (blockElements[name]) {
3846 							if (!isInWhiteSpacePreservedElement) {
3847 								// Trim whitespace of the first node in a block
3848 								textNode = node.firstChild;
3849 								if (textNode && textNode.type === 3) {
3850 									text = textNode.value.replace(startWhiteSpaceRegExp, '');
3851 
3852 									// Any characters left after trim or should we remove it
3853 									if (text.length > 0) {
3854 										textNode.value = text;
3855 										textNode = textNode.next;
3856 									} else {
3857 										sibling = textNode.next;
3858 										textNode.remove();
3859 										textNode = sibling;
3860 									}
3861 
3862 									// Remove any pure whitespace siblings
3863 									while (textNode && textNode.type === 3) {
3864 										text = textNode.value;
3865 										sibling = textNode.next;
3866 
3867 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3868 											textNode.remove();
3869 											textNode = sibling;
3870 										}
3871 
3872 										textNode = sibling;
3873 									}
3874 								}
3875 
3876 								// Trim whitespace of the last node in a block
3877 								textNode = node.lastChild;
3878 								if (textNode && textNode.type === 3) {
3879 									text = textNode.value.replace(endWhiteSpaceRegExp, '');
3880 
3881 									// Any characters left after trim or should we remove it
3882 									if (text.length > 0) {
3883 										textNode.value = text;
3884 										textNode = textNode.prev;
3885 									} else {
3886 										sibling = textNode.prev;
3887 										textNode.remove();
3888 										textNode = sibling;
3889 									}
3890 
3891 									// Remove any pure whitespace siblings
3892 									while (textNode && textNode.type === 3) {
3893 										text = textNode.value;
3894 										sibling = textNode.prev;
3895 
3896 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3897 											textNode.remove();
3898 											textNode = sibling;
3899 										}
3900 
3901 										textNode = sibling;
3902 									}
3903 								}
3904 							}
3905 
3906 							// Trim start white space
3907 							textNode = node.prev;
3908 							if (textNode && textNode.type === 3) {
3909 								text = textNode.value.replace(startWhiteSpaceRegExp, '');
3910 
3911 								if (text.length > 0)
3912 									textNode.value = text;
3913 								else
3914 									textNode.remove();
3915 							}
3916 						}
3917 
3918 						// Check if we exited a whitespace preserved element
3919 						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3920 							isInWhiteSpacePreservedElement = false;
3921 						}
3922 
3923 						// Handle empty nodes
3924 						if (elementRule.removeEmpty || elementRule.paddEmpty) {
3925 							if (node.isEmpty(nonEmptyElements)) {
3926 								if (elementRule.paddEmpty)
3927 									node.empty().append(new Node('#text', '3')).value = '\u00a0';
3928 								else {
3929 									// Leave nodes that have a name like <a name="name">
3930 									if (!node.attributes.map.name && !node.attributes.map.id) {
3931 										tempNode = node.parent;
3932 										node.empty().remove();
3933 										node = tempNode;
3934 										return;
3935 									}
3936 								}
3937 							}
3938 						}
3939 
3940 						node = node.parent;
3941 					}
3942 				}
3943 			}, schema);
3944 
3945 			rootNode = node = new Node(args.context || settings.root_name, 11);
3946 
3947 			parser.parse(html);
3948 
3949 			// Fix invalid children or report invalid children in a contextual parsing
3950 			if (validate && invalidChildren.length) {
3951 				if (!args.context)
3952 					fixInvalidChildren(invalidChildren);
3953 				else
3954 					args.invalid = true;
3955 			}
3956 
3957 			// Wrap nodes in the root into block elements if the root is body
3958 			if (rootBlockName && rootNode.name == 'body')
3959 				addRootBlocks();
3960 
3961 			// Run filters only when the contents is valid
3962 			if (!args.invalid) {
3963 				// Run node filters
3964 				for (name in matchedNodes) {
3965 					list = nodeFilters[name];
3966 					nodes = matchedNodes[name];
3967 
3968 					// Remove already removed children
3969 					fi = nodes.length;
3970 					while (fi--) {
3971 						if (!nodes[fi].parent)
3972 							nodes.splice(fi, 1);
3973 					}
3974 
3975 					for (i = 0, l = list.length; i < l; i++)
3976 						list[i](nodes, name, args);
3977 				}
3978 
3979 				// Run attribute filters
3980 				for (i = 0, l = attributeFilters.length; i < l; i++) {
3981 					list = attributeFilters[i];
3982 
3983 					if (list.name in matchedAttributes) {
3984 						nodes = matchedAttributes[list.name];
3985 
3986 						// Remove already removed children
3987 						fi = nodes.length;
3988 						while (fi--) {
3989 							if (!nodes[fi].parent)
3990 								nodes.splice(fi, 1);
3991 						}
3992 
3993 						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
3994 							list.callbacks[fi](nodes, list.name, args);
3995 					}
3996 				}
3997 			}
3998 
3999 			return rootNode;
4000 		};
4001 
4002 		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
4003 		// make it possible to place the caret inside empty blocks. This logic tries to remove
4004 		// these elements and keep br elements that where intended to be there intact
4005 		if (settings.remove_trailing_brs) {
4006 			self.addNodeFilter('br', function(nodes, name) {
4007 				var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
4008 					nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
4009 
4010 				// Remove brs from body element as well
4011 				blockElements.body = 1;
4012 
4013 				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
4014 				for (i = 0; i < l; i++) {
4015 					node = nodes[i];
4016 					parent = node.parent;
4017 
4018 					if (blockElements[node.parent.name] && node === parent.lastChild) {
4019 						// Loop all nodes to the left of the current node and check for other BR elements
4020 						// excluding bookmarks since they are invisible
4021 						prev = node.prev;
4022 						while (prev) {
4023 							prevName = prev.name;
4024 
4025 							// Ignore bookmarks
4026 							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4027 								// Found a non BR element
4028 								if (prevName !== "br")
4029 									break;
4030 	
4031 								// Found another br it's a <br><br> structure then don't remove anything
4032 								if (prevName === 'br') {
4033 									node = null;
4034 									break;
4035 								}
4036 							}
4037 
4038 							prev = prev.prev;
4039 						}
4040 
4041 						if (node) {
4042 							node.remove();
4043 
4044 							// Is the parent to be considered empty after we removed the BR
4045 							if (parent.isEmpty(nonEmptyElements)) {
4046 								elementRule = schema.getElementRule(parent.name);
4047 
4048 								// Remove or padd the element depending on schema rule
4049 								if (elementRule) {
4050 									if (elementRule.removeEmpty)
4051 										parent.remove();
4052 									else if (elementRule.paddEmpty)
4053 										parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4054 								}
4055 							}
4056 						}
4057 					} else {
4058 						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 
4059 						lastParent = node;
4060 						while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4061 							lastParent = parent;
4062 
4063 							if (blockElements[parent.name]) {
4064 								break;
4065 							}
4066 
4067 							parent = parent.parent;
4068 						}
4069 
4070 						if (lastParent === parent) {
4071 							textNode = new tinymce.html.Node('#text', 3);
4072 							textNode.value = '\u00a0';
4073 							node.replace(textNode);
4074 						}
4075 					}
4076 				}
4077 			});
4078 		}
4079 
4080 		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4081 		if (!settings.allow_html_in_named_anchor) {
4082 			self.addAttributeFilter('id,name', function(nodes, name) {
4083 				var i = nodes.length, sibling, prevSibling, parent, node;
4084 
4085 				while (i--) {
4086 					node = nodes[i];
4087 					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4088 						parent = node.parent;
4089 
4090 						// Move children after current node
4091 						sibling = node.lastChild;
4092 						do {
4093 							prevSibling = sibling.prev;
4094 							parent.insert(sibling, node);
4095 							sibling = prevSibling;
4096 						} while (sibling);
4097 					}
4098 				}
4099 			});
4100 		}
4101 	}
4102 })(tinymce);
4103 
4104 tinymce.html.Writer = function(settings) {
4105 	var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4106 
4107 	settings = settings || {};
4108 	indent = settings.indent;
4109 	indentBefore = tinymce.makeMap(settings.indent_before || '');
4110 	indentAfter = tinymce.makeMap(settings.indent_after || '');
4111 	encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4112 	htmlOutput = settings.element_format == "html";
4113 
4114 	return {
4115 		start: function(name, attrs, empty) {
4116 			var i, l, attr, value;
4117 
4118 			if (indent && indentBefore[name] && html.length > 0) {
4119 				value = html[html.length - 1];
4120 
4121 				if (value.length > 0 && value !== '\n')
4122 					html.push('\n');
4123 			}
4124 
4125 			html.push('<', name);
4126 
4127 			if (attrs) {
4128 				for (i = 0, l = attrs.length; i < l; i++) {
4129 					attr = attrs[i];
4130 					html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4131 				}
4132 			}
4133 
4134 			if (!empty || htmlOutput)
4135 				html[html.length] = '>';
4136 			else
4137 				html[html.length] = ' />';
4138 
4139 			if (empty && indent && indentAfter[name] && html.length > 0) {
4140 				value = html[html.length - 1];
4141 
4142 				if (value.length > 0 && value !== '\n')
4143 					html.push('\n');
4144 			}
4145 		},
4146 
4147 		end: function(name) {
4148 			var value;
4149 
4150 			/*if (indent && indentBefore[name] && html.length > 0) {
4151 				value = html[html.length - 1];
4152 
4153 				if (value.length > 0 && value !== '\n')
4154 					html.push('\n');
4155 			}*/
4156 
4157 			html.push('</', name, '>');
4158 
4159 			if (indent && indentAfter[name] && html.length > 0) {
4160 				value = html[html.length - 1];
4161 
4162 				if (value.length > 0 && value !== '\n')
4163 					html.push('\n');
4164 			}
4165 		},
4166 
4167 		text: function(text, raw) {
4168 			if (text.length > 0)
4169 				html[html.length] = raw ? text : encode(text);
4170 		},
4171 
4172 		cdata: function(text) {
4173 			html.push('<![CDATA[', text, ']]>');
4174 		},
4175 
4176 		comment: function(text) {
4177 			html.push('<!--', text, '-->');
4178 		},
4179 
4180 		pi: function(name, text) {
4181 			if (text)
4182 				html.push('<?', name, ' ', text, '?>');
4183 			else
4184 				html.push('<?', name, '?>');
4185 
4186 			if (indent)
4187 				html.push('\n');
4188 		},
4189 
4190 		doctype: function(text) {
4191 			html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4192 		},
4193 
4194 		reset: function() {
4195 			html.length = 0;
4196 		},
4197 
4198 		getContent: function() {
4199 			return html.join('').replace(/\n$/, '');
4200 		}
4201 	};
4202 };
4203 
4204 (function(tinymce) {
4205 	tinymce.html.Serializer = function(settings, schema) {
4206 		var self = this, writer = new tinymce.html.Writer(settings);
4207 
4208 		settings = settings || {};
4209 		settings.validate = "validate" in settings ? settings.validate : true;
4210 
4211 		self.schema = schema = schema || new tinymce.html.Schema();
4212 		self.writer = writer;
4213 
4214 		self.serialize = function(node) {
4215 			var handlers, validate;
4216 
4217 			validate = settings.validate;
4218 
4219 			handlers = {
4220 				// #text
4221 				3: function(node, raw) {
4222 					writer.text(node.value, node.raw);
4223 				},
4224 
4225 				// #comment
4226 				8: function(node) {
4227 					writer.comment(node.value);
4228 				},
4229 
4230 				// Processing instruction
4231 				7: function(node) {
4232 					writer.pi(node.name, node.value);
4233 				},
4234 
4235 				// Doctype
4236 				10: function(node) {
4237 					writer.doctype(node.value);
4238 				},
4239 
4240 				// CDATA
4241 				4: function(node) {
4242 					writer.cdata(node.value);
4243 				},
4244 
4245 				// Document fragment
4246 				11: function(node) {
4247 					if ((node = node.firstChild)) {
4248 						do {
4249 							walk(node);
4250 						} while (node = node.next);
4251 					}
4252 				}
4253 			};
4254 
4255 			writer.reset();
4256 
4257 			function walk(node) {
4258 				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4259 
4260 				if (!handler) {
4261 					name = node.name;
4262 					isEmpty = node.shortEnded;
4263 					attrs = node.attributes;
4264 
4265 					// Sort attributes
4266 					if (validate && attrs && attrs.length > 1) {
4267 						sortedAttrs = [];
4268 						sortedAttrs.map = {};
4269 
4270 						elementRule = schema.getElementRule(node.name);
4271 						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4272 							attrName = elementRule.attributesOrder[i];
4273 
4274 							if (attrName in attrs.map) {
4275 								attrValue = attrs.map[attrName];
4276 								sortedAttrs.map[attrName] = attrValue;
4277 								sortedAttrs.push({name: attrName, value: attrValue});
4278 							}
4279 						}
4280 
4281 						for (i = 0, l = attrs.length; i < l; i++) {
4282 							attrName = attrs[i].name;
4283 
4284 							if (!(attrName in sortedAttrs.map)) {
4285 								attrValue = attrs.map[attrName];
4286 								sortedAttrs.map[attrName] = attrValue;
4287 								sortedAttrs.push({name: attrName, value: attrValue});
4288 							}
4289 						}
4290 
4291 						attrs = sortedAttrs;
4292 					}
4293 
4294 					writer.start(node.name, attrs, isEmpty);
4295 
4296 					if (!isEmpty) {
4297 						if ((node = node.firstChild)) {
4298 							do {
4299 								walk(node);
4300 							} while (node = node.next);
4301 						}
4302 
4303 						writer.end(name);
4304 					}
4305 				} else
4306 					handler(node);
4307 			}
4308 
4309 			// Serialize element and treat all non elements as fragments
4310 			if (node.type == 1 && !settings.inner)
4311 				walk(node);
4312 			else
4313 				handlers[11](node);
4314 
4315 			return writer.getContent();
4316 		};
4317 	}
4318 })(tinymce);
4319 
4320 // JSLint defined globals
4321 /*global tinymce:false, window:false */
4322 
4323 tinymce.dom = {};
4324 
4325 (function(namespace, expando) {
4326 	var w3cEventModel = !!document.addEventListener;
4327 
4328 	function addEvent(target, name, callback, capture) {
4329 		if (target.addEventListener) {
4330 			target.addEventListener(name, callback, capture || false);
4331 		} else if (target.attachEvent) {
4332 			target.attachEvent('on' + name, callback);
4333 		}
4334 	}
4335 
4336 	function removeEvent(target, name, callback, capture) {
4337 		if (target.removeEventListener) {
4338 			target.removeEventListener(name, callback, capture || false);
4339 		} else if (target.detachEvent) {
4340 			target.detachEvent('on' + name, callback);
4341 		}
4342 	}
4343 
4344 	function fix(original_event, data) {
4345 		var name, event = data || {};
4346 
4347 		// Dummy function that gets replaced on the delegation state functions
4348 		function returnFalse() {
4349 			return false;
4350 		}
4351 
4352 		// Dummy function that gets replaced on the delegation state functions
4353 		function returnTrue() {
4354 			return true;
4355 		}
4356 
4357 		// Copy all properties from the original event
4358 		for (name in original_event) {
4359 			// layerX/layerY is deprecated in Chrome and produces a warning
4360 			if (name !== "layerX" && name !== "layerY") {
4361 				event[name] = original_event[name];
4362 			}
4363 		}
4364 
4365 		// Normalize target IE uses srcElement
4366 		if (!event.target) {
4367 			event.target = event.srcElement || document;
4368 		}
4369 
4370 		// Add preventDefault method
4371 		event.preventDefault = function() {
4372 			event.isDefaultPrevented = returnTrue;
4373 
4374 			// Execute preventDefault on the original event object
4375 			if (original_event) {
4376 				if (original_event.preventDefault) {
4377 					original_event.preventDefault();
4378 				} else {
4379 					original_event.returnValue = false; // IE
4380 				}
4381 			}
4382 		};
4383 
4384 		// Add stopPropagation
4385 		event.stopPropagation = function() {
4386 			event.isPropagationStopped = returnTrue;
4387 
4388 			// Execute stopPropagation on the original event object
4389 			if (original_event) {
4390 				if (original_event.stopPropagation) {
4391 					original_event.stopPropagation();
4392 				} else {
4393 					original_event.cancelBubble = true; // IE
4394 				}
4395 			 }
4396 		};
4397 
4398 		// Add stopImmediatePropagation
4399 		event.stopImmediatePropagation = function() {
4400 			event.isImmediatePropagationStopped = returnTrue;
4401 			event.stopPropagation();
4402 		};
4403 
4404 		// Add event delegation states
4405 		if (!event.isDefaultPrevented) {
4406 			event.isDefaultPrevented = returnFalse;
4407 			event.isPropagationStopped = returnFalse;
4408 			event.isImmediatePropagationStopped = returnFalse;
4409 		}
4410 
4411 		return event;
4412 	}
4413 
4414 	function bindOnReady(win, callback, event_utils) {
4415 		var doc = win.document, event = {type: 'ready'};
4416 
4417 		// Gets called when the DOM is ready
4418 		function readyHandler() {
4419 			if (!event_utils.domLoaded) {
4420 				event_utils.domLoaded = true;
4421 				callback(event);
4422 			}
4423 		}
4424 
4425 		// Use W3C method
4426 		if (w3cEventModel) {
4427 			addEvent(win, 'DOMContentLoaded', readyHandler);
4428 		} else {
4429 			// Use IE method
4430 			addEvent(doc, "readystatechange", function() {
4431 				if (doc.readyState === "complete") {
4432 					removeEvent(doc, "readystatechange", arguments.callee);
4433 					readyHandler();
4434 				}
4435 			});
4436 
4437 			// Wait until we can scroll, when we can the DOM is initialized
4438 			if (doc.documentElement.doScroll && win === win.top) {
4439 				(function() {
4440 					try {
4441 						// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4442 						// http://javascript.nwbox.com/IEContentLoaded/
4443 						doc.documentElement.doScroll("left");
4444 					} catch (ex) {
4445 						setTimeout(arguments.callee, 0);
4446 						return;
4447 					}
4448 
4449 					readyHandler();
4450 				})();
4451 			}
4452 		}
4453 
4454 		// Fallback if any of the above methods should fail for some odd reason
4455 		addEvent(win, 'load', readyHandler);
4456 	}
4457 
4458 	function EventUtils(proxy) {
4459 		var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4460 
4461 		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4462 		hasFocusIn = "onfocusin" in document.documentElement;
4463 		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4464 		count = 1;
4465 
4466 		// State if the DOMContentLoaded was executed or not
4467 		self.domLoaded = false;
4468 		self.events = events;
4469 
4470 		function executeHandlers(evt, id) {
4471 			var callbackList, i, l, callback;
4472 
4473 			callbackList = events[id][evt.type];
4474 			if (callbackList) {
4475 				for (i = 0, l = callbackList.length; i < l; i++) {
4476 					callback = callbackList[i];
4477 					
4478 					// Check if callback exists might be removed if a unbind is called inside the callback
4479 					if (callback && callback.func.call(callback.scope, evt) === false) {
4480 						evt.preventDefault();
4481 					}
4482 
4483 					// Should we stop propagation to immediate listeners
4484 					if (evt.isImmediatePropagationStopped()) {
4485 						return;
4486 					}
4487 				}
4488 			}
4489 		}
4490 
4491 		self.bind = function(target, names, callback, scope) {
4492 			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4493 
4494 			// Native event handler function patches the event and executes the callbacks for the expando
4495 			function defaultNativeHandler(evt) {
4496 				executeHandlers(fix(evt || win.event), id);
4497 			}
4498 
4499 			// Don't bind to text nodes or comments
4500 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4501 				return;
4502 			}
4503 
4504 			// Create or get events id for the target
4505 			if (!target[expando]) {
4506 				id = count++;
4507 				target[expando] = id;
4508 				events[id] = {};
4509 			} else {
4510 				id = target[expando];
4511 
4512 				if (!events[id]) {
4513 					events[id] = {};
4514 				}
4515 			}
4516 
4517 			// Setup the specified scope or use the target as a default
4518 			scope = scope || target;
4519 
4520 			// Split names and bind each event, enables you to bind multiple events with one call
4521 			names = names.split(' ');
4522 			i = names.length;
4523 			while (i--) {
4524 				name = names[i];
4525 				nativeHandler = defaultNativeHandler;
4526 				fakeName = capture = false;
4527 
4528 				// Use ready instead of DOMContentLoaded
4529 				if (name === "DOMContentLoaded") {
4530 					name = "ready";
4531 				}
4532 
4533 				// DOM is already ready
4534 				if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4535 					self.domLoaded = true;
4536 					callback.call(scope, fix({type: name}));
4537 					continue;
4538 				}
4539 
4540 				// Handle mouseenter/mouseleaver
4541 				if (!hasMouseEnterLeave) {
4542 					fakeName = mouseEnterLeave[name];
4543 
4544 					if (fakeName) {
4545 						nativeHandler = function(evt) {
4546 							var current, related;
4547 
4548 							current = evt.currentTarget;
4549 							related = evt.relatedTarget;
4550 
4551 							// Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4552 							if (related && current.contains) {
4553 								// Use contains for performance
4554 								related = current.contains(related);
4555 							} else {
4556 								while (related && related !== current) {
4557 									related = related.parentNode;
4558 								}
4559 							}
4560 
4561 							// Fire fake event
4562 							if (!related) {
4563 								evt = fix(evt || win.event);
4564 								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4565 								evt.target = current;
4566 								executeHandlers(evt, id);
4567 							}
4568 						};
4569 					}
4570 				}
4571 
4572 				// Fake bubbeling of focusin/focusout
4573 				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4574 					capture = true;
4575 					fakeName = name === "focusin" ? "focus" : "blur";
4576 					nativeHandler = function(evt) {
4577 						evt = fix(evt || win.event);
4578 						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4579 						executeHandlers(evt, id);
4580 					};
4581 				}
4582 
4583 				// Setup callback list and bind native event
4584 				callbackList = events[id][name];
4585 				if (!callbackList) {
4586 					events[id][name] = callbackList = [{func: callback, scope: scope}];
4587 					callbackList.fakeName = fakeName;
4588 					callbackList.capture = capture;
4589 
4590 					// Add the nativeHandler to the callback list so that we can later unbind it
4591 					callbackList.nativeHandler = nativeHandler;
4592 					if (!w3cEventModel) {
4593 						callbackList.proxyHandler = proxy(id);
4594 					}
4595 
4596 					// Check if the target has native events support
4597 					if (name === "ready") {
4598 						bindOnReady(target, nativeHandler, self);
4599 					} else {
4600 						addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4601 					}
4602 				} else {
4603 					// If it already has an native handler then just push the callback
4604 					callbackList.push({func: callback, scope: scope});
4605 				}
4606 			}
4607 
4608 			target = callbackList = 0; // Clean memory for IE
4609 
4610 			return callback;
4611 		};
4612 
4613 		self.unbind = function(target, names, callback) {
4614 			var id, callbackList, i, ci, name, eventMap;
4615 
4616 			// Don't bind to text nodes or comments
4617 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4618 				return self;
4619 			}
4620 
4621 			// Unbind event or events if the target has the expando
4622 			id = target[expando];
4623 			if (id) {
4624 				eventMap = events[id];
4625 
4626 				// Specific callback
4627 				if (names) {
4628 					names = names.split(' ');
4629 					i = names.length;
4630 					while (i--) {
4631 						name = names[i];
4632 						callbackList = eventMap[name];
4633 
4634 						// Unbind the event if it exists in the map
4635 						if (callbackList) {
4636 							// Remove specified callback
4637 							if (callback) {
4638 								ci = callbackList.length;
4639 								while (ci--) {
4640 									if (callbackList[ci].func === callback) {
4641 										callbackList.splice(ci, 1);
4642 									}
4643 								}
4644 							}
4645 
4646 							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
4647 							if (!callback || callbackList.length === 0) {
4648 								delete eventMap[name];
4649 								removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4650 							}
4651 						}
4652 					}
4653 				} else {
4654 					// All events for a specific element
4655 					for (name in eventMap) {
4656 						callbackList = eventMap[name];
4657 						removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4658 					}
4659 
4660 					eventMap = {};
4661 				}
4662 
4663 				// Check if object is empty, if it isn't then we won't remove the expando map
4664 				for (name in eventMap) {
4665 					return self;
4666 				}
4667 
4668 				// Delete event object
4669 				delete events[id];
4670 
4671 				// Remove expando from target
4672 				try {
4673 					// IE will fail here since it can't delete properties from window
4674 					delete target[expando];
4675 				} catch (ex) {
4676 					// IE will set it to null
4677 					target[expando] = null;
4678 				}
4679 			}
4680 
4681 			return self;
4682 		};
4683 
4684 		self.fire = function(target, name, args) {
4685 			var id, event;
4686 
4687 			// Don't bind to text nodes or comments
4688 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4689 				return self;
4690 			}
4691 
4692 			// Build event object by patching the args
4693 			event = fix(null, args);
4694 			event.type = name;
4695 
4696 			do {
4697 				// Found an expando that means there is listeners to execute
4698 				id = target[expando];
4699 				if (id) {
4700 					executeHandlers(event, id);
4701 				}
4702 
4703 				// Walk up the DOM
4704 				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
4705 			} while (target && !event.isPropagationStopped());
4706 
4707 			return self;
4708 		};
4709 
4710 		self.clean = function(target) {
4711 			var i, children, unbind = self.unbind;
4712 	
4713 			// Don't bind to text nodes or comments
4714 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4715 				return self;
4716 			}
4717 
4718 			// Unbind any element on the specificed target
4719 			if (target[expando]) {
4720 				unbind(target);
4721 			}
4722 
4723 			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
4724 			if (!target.getElementsByTagName) {
4725 				target = target.document;
4726 			}
4727 
4728 			// Remove events from each child element
4729 			if (target && target.getElementsByTagName) {
4730 				unbind(target);
4731 
4732 				children = target.getElementsByTagName('*');
4733 				i = children.length;
4734 				while (i--) {
4735 					target = children[i];
4736 
4737 					if (target[expando]) {
4738 						unbind(target);
4739 					}
4740 				}
4741 			}
4742 
4743 			return self;
4744 		};
4745 
4746 		self.callNativeHandler = function(id, evt) {
4747 			if (events) {
4748 				events[id][evt.type].nativeHandler(evt);
4749 			}
4750 		};
4751 
4752 		self.destory = function() {
4753 			events = {};
4754 		};
4755 
4756 		// Legacy function calls
4757 
4758 		self.add = function(target, events, func, scope) {
4759 			// Old API supported direct ID assignment
4760 			if (typeof(target) === "string") {
4761 				target = document.getElementById(target);
4762 			}
4763 
4764 			// Old API supported multiple targets
4765 			if (target && target instanceof Array) {
4766 				var i = target.length;
4767 
4768 				while (i--) {
4769 					self.add(target[i], events, func, scope);
4770 				}
4771 
4772 				return;
4773 			}
4774 
4775 			// Old API called ready init
4776 			if (events === "init") {
4777 				events = "ready";
4778 			}
4779 
4780 			return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
4781 		};
4782 
4783 		self.remove = function(target, events, func, scope) {
4784 			if (!target) {
4785 				return self;
4786 			}
4787 
4788 			// Old API supported direct ID assignment
4789 			if (typeof(target) === "string") {
4790 				target = document.getElementById(target);
4791 			}
4792 
4793 			// Old API supported multiple targets
4794 			if (target instanceof Array) {
4795 				var i = target.length;
4796 
4797 				while (i--) {
4798 					self.remove(target[i], events, func, scope);
4799 				}
4800 
4801 				return self;
4802 			}
4803 
4804 			return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
4805 		};
4806 
4807 		self.clear = function(target) {
4808 			// Old API supported direct ID assignment
4809 			if (typeof(target) === "string") {
4810 				target = document.getElementById(target);
4811 			}
4812 
4813 			return self.clean(target);
4814 		};
4815 
4816 		self.cancel = function(e) {
4817 			if (e) {
4818 				self.prevent(e);
4819 				self.stop(e);
4820 			}
4821 
4822 			return false;
4823 		};
4824 
4825 		self.prevent = function(e) {
4826 			if (!e.preventDefault) {
4827 				e = fix(e);
4828 			}
4829 
4830 			e.preventDefault();
4831 
4832 			return false;
4833 		};
4834 
4835 		self.stop = function(e) {
4836 			if (!e.stopPropagation) {
4837 				e = fix(e);
4838 			}
4839 
4840 			e.stopPropagation();
4841 
4842 			return false;
4843 		};
4844 	}
4845 
4846 	namespace.EventUtils = EventUtils;
4847 
4848 	namespace.Event = new EventUtils(function(id) {
4849 		return function(evt) {
4850 			tinymce.dom.Event.callNativeHandler(id, evt);
4851 		};
4852 	});
4853 
4854 	// Bind ready event when tinymce script is loaded
4855 	namespace.Event.bind(window, 'ready', function() {});
4856 
4857 	namespace = 0;
4858 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
4859 
4860 tinymce.dom.TreeWalker = function(start_node, root_node) {
4861 	var node = start_node;
4862 
4863 	function findSibling(node, start_name, sibling_name, shallow) {
4864 		var sibling, parent;
4865 
4866 		if (node) {
4867 			// Walk into nodes if it has a start
4868 			if (!shallow && node[start_name])
4869 				return node[start_name];
4870 
4871 			// Return the sibling if it has one
4872 			if (node != root_node) {
4873 				sibling = node[sibling_name];
4874 				if (sibling)
4875 					return sibling;
4876 
4877 				// Walk up the parents to look for siblings
4878 				for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
4879 					sibling = parent[sibling_name];
4880 					if (sibling)
4881 						return sibling;
4882 				}
4883 			}
4884 		}
4885 	};
4886 
4887 	this.current = function() {
4888 		return node;
4889 	};
4890 
4891 	this.next = function(shallow) {
4892 		return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
4893 	};
4894 
4895 	this.prev = function(shallow) {
4896 		return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
4897 	};
4898 };
4899 
4900 (function(tinymce) {
4901 	// Shorten names
4902 	var each = tinymce.each,
4903 		is = tinymce.is,
4904 		isWebKit = tinymce.isWebKit,
4905 		isIE = tinymce.isIE,
4906 		Entities = tinymce.html.Entities,
4907 		simpleSelectorRe = /^([a-z0-9],?)+$/i,
4908 		whiteSpaceRegExp = /^[ \t\r\n]*$/;
4909 
4910 	tinymce.create('tinymce.dom.DOMUtils', {
4911 		doc : null,
4912 		root : null,
4913 		files : null,
4914 		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
4915 		props : {
4916 			"for" : "htmlFor",
4917 			"class" : "className",
4918 			className : "className",
4919 			checked : "checked",
4920 			disabled : "disabled",
4921 			maxlength : "maxLength",
4922 			readonly : "readOnly",
4923 			selected : "selected",
4924 			value : "value",
4925 			id : "id",
4926 			name : "name",
4927 			type : "type"
4928 		},
4929 
4930 		DOMUtils : function(d, s) {
4931 			var t = this, globalStyle, name, blockElementsMap;
4932 
4933 			t.doc = d;
4934 			t.win = window;
4935 			t.files = {};
4936 			t.cssFlicker = false;
4937 			t.counter = 0;
4938 			t.stdMode = !tinymce.isIE || d.documentMode >= 8;
4939 			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
4940 			t.hasOuterHTML = "outerHTML" in d.createElement("a");
4941 
4942 			t.settings = s = tinymce.extend({
4943 				keep_values : false,
4944 				hex_colors : 1
4945 			}, s);
4946 			
4947 			t.schema = s.schema;
4948 			t.styles = new tinymce.html.Styles({
4949 				url_converter : s.url_converter,
4950 				url_converter_scope : s.url_converter_scope
4951 			}, s.schema);
4952 
4953 			// Fix IE6SP2 flicker and check it failed for pre SP2
4954 			if (tinymce.isIE6) {
4955 				try {
4956 					d.execCommand('BackgroundImageCache', false, true);
4957 				} catch (e) {
4958 					t.cssFlicker = true;
4959 				}
4960 			}
4961 
4962 			t.fixDoc(d);
4963 			t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
4964 			tinymce.addUnload(t.destroy, t);
4965 			blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
4966 
4967 			t.isBlock = function(node) {
4968 				// This function is called in module pattern style since it might be executed with the wrong this scope
4969 				var type = node.nodeType;
4970 
4971 				// If it's a node then check the type and use the nodeName
4972 				if (type)
4973 					return !!(type === 1 && blockElementsMap[node.nodeName]);
4974 
4975 				return !!blockElementsMap[node];
4976 			};
4977 		},
4978 
4979 		fixDoc: function(doc) {
4980 			var settings = this.settings, name;
4981 
4982 			if (isIE && settings.schema) {
4983 				// Add missing HTML 4/5 elements to IE
4984 				('abbr article aside audio canvas ' +
4985 				'details figcaption figure footer ' +
4986 				'header hgroup mark menu meter nav ' +
4987 				'output progress section summary ' +
4988 				'time video').replace(/\w+/g, function(name) {
4989 					doc.createElement(name);
4990 				});
4991 
4992 				// Create all custom elements
4993 				for (name in settings.schema.getCustomElements()) {
4994 					doc.createElement(name);
4995 				}
4996 			}
4997 		},
4998 
4999 		clone: function(node, deep) {
5000 			var self = this, clone, doc;
5001 
5002 			// TODO: Add feature detection here in the future
5003 			if (!isIE || node.nodeType !== 1 || deep) {
5004 				return node.cloneNode(deep);
5005 			}
5006 
5007 			doc = self.doc;
5008 
5009 			// Make a HTML5 safe shallow copy
5010 			if (!deep) {
5011 				clone = doc.createElement(node.nodeName);
5012 
5013 				// Copy attribs
5014 				each(self.getAttribs(node), function(attr) {
5015 					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5016 				});
5017 
5018 				return clone;
5019 			}
5020 /*
5021 			// Setup HTML5 patched document fragment
5022 			if (!self.frag) {
5023 				self.frag = doc.createDocumentFragment();
5024 				self.fixDoc(self.frag);
5025 			}
5026 
5027 			// Make a deep copy by adding it to the document fragment then removing it this removed the :section
5028 			clone = doc.createElement('div');
5029 			self.frag.appendChild(clone);
5030 			clone.innerHTML = node.outerHTML;
5031 			self.frag.removeChild(clone);
5032 */
5033 			return clone.firstChild;
5034 		},
5035 
5036 		getRoot : function() {
5037 			var t = this, s = t.settings;
5038 
5039 			return (s && t.get(s.root_element)) || t.doc.body;
5040 		},
5041 
5042 		getViewPort : function(w) {
5043 			var d, b;
5044 
5045 			w = !w ? this.win : w;
5046 			d = w.document;
5047 			b = this.boxModel ? d.documentElement : d.body;
5048 
5049 			// Returns viewport size excluding scrollbars
5050 			return {
5051 				x : w.pageXOffset || b.scrollLeft,
5052 				y : w.pageYOffset || b.scrollTop,
5053 				w : w.innerWidth || b.clientWidth,
5054 				h : w.innerHeight || b.clientHeight
5055 			};
5056 		},
5057 
5058 		getRect : function(e) {
5059 			var p, t = this, sr;
5060 
5061 			e = t.get(e);
5062 			p = t.getPos(e);
5063 			sr = t.getSize(e);
5064 
5065 			return {
5066 				x : p.x,
5067 				y : p.y,
5068 				w : sr.w,
5069 				h : sr.h
5070 			};
5071 		},
5072 
5073 		getSize : function(e) {
5074 			var t = this, w, h;
5075 
5076 			e = t.get(e);
5077 			w = t.getStyle(e, 'width');
5078 			h = t.getStyle(e, 'height');
5079 
5080 			// Non pixel value, then force offset/clientWidth
5081 			if (w.indexOf('px') === -1)
5082 				w = 0;
5083 
5084 			// Non pixel value, then force offset/clientWidth
5085 			if (h.indexOf('px') === -1)
5086 				h = 0;
5087 
5088 			return {
5089 				w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5090 				h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5091 			};
5092 		},
5093 
5094 		getParent : function(n, f, r) {
5095 			return this.getParents(n, f, r, false);
5096 		},
5097 
5098 		getParents : function(n, f, r, c) {
5099 			var t = this, na, se = t.settings, o = [];
5100 
5101 			n = t.get(n);
5102 			c = c === undefined;
5103 
5104 			if (se.strict_root)
5105 				r = r || t.getRoot();
5106 
5107 			// Wrap node name as func
5108 			if (is(f, 'string')) {
5109 				na = f;
5110 
5111 				if (f === '*') {
5112 					f = function(n) {return n.nodeType == 1;};
5113 				} else {
5114 					f = function(n) {
5115 						return t.is(n, na);
5116 					};
5117 				}
5118 			}
5119 
5120 			while (n) {
5121 				if (n == r || !n.nodeType || n.nodeType === 9)
5122 					break;
5123 
5124 				if (!f || f(n)) {
5125 					if (c)
5126 						o.push(n);
5127 					else
5128 						return n;
5129 				}
5130 
5131 				n = n.parentNode;
5132 			}
5133 
5134 			return c ? o : null;
5135 		},
5136 
5137 		get : function(e) {
5138 			var n;
5139 
5140 			if (e && this.doc && typeof(e) == 'string') {
5141 				n = e;
5142 				e = this.doc.getElementById(e);
5143 
5144 				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5145 				if (e && e.id !== n)
5146 					return this.doc.getElementsByName(n)[1];
5147 			}
5148 
5149 			return e;
5150 		},
5151 
5152 		getNext : function(node, selector) {
5153 			return this._findSib(node, selector, 'nextSibling');
5154 		},
5155 
5156 		getPrev : function(node, selector) {
5157 			return this._findSib(node, selector, 'previousSibling');
5158 		},
5159 
5160 
5161 		select : function(pa, s) {
5162 			var t = this;
5163 
5164 			return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
5165 		},
5166 
5167 		is : function(n, selector) {
5168 			var i;
5169 
5170 			// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
5171 			if (n.length === undefined) {
5172 				// Simple all selector
5173 				if (selector === '*')
5174 					return n.nodeType == 1;
5175 
5176 				// Simple selector just elements
5177 				if (simpleSelectorRe.test(selector)) {
5178 					selector = selector.toLowerCase().split(/,/);
5179 					n = n.nodeName.toLowerCase();
5180 
5181 					for (i = selector.length - 1; i >= 0; i--) {
5182 						if (selector[i] == n)
5183 							return true;
5184 					}
5185 
5186 					return false;
5187 				}
5188 			}
5189 
5190 			return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
5191 		},
5192 
5193 
5194 		add : function(p, n, a, h, c) {
5195 			var t = this;
5196 
5197 			return this.run(p, function(p) {
5198 				var e, k;
5199 
5200 				e = is(n, 'string') ? t.doc.createElement(n) : n;
5201 				t.setAttribs(e, a);
5202 
5203 				if (h) {
5204 					if (h.nodeType)
5205 						e.appendChild(h);
5206 					else
5207 						t.setHTML(e, h);
5208 				}
5209 
5210 				return !c ? p.appendChild(e) : e;
5211 			});
5212 		},
5213 
5214 		create : function(n, a, h) {
5215 			return this.add(this.doc.createElement(n), n, a, h, 1);
5216 		},
5217 
5218 		createHTML : function(n, a, h) {
5219 			var o = '', t = this, k;
5220 
5221 			o += '<' + n;
5222 
5223 			for (k in a) {
5224 				if (a.hasOwnProperty(k))
5225 					o += ' ' + k + '="' + t.encode(a[k]) + '"';
5226 			}
5227 
5228 			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5229 			if (typeof(h) != "undefined")
5230 				return o + '>' + h + '</' + n + '>';
5231 
5232 			return o + ' />';
5233 		},
5234 
5235 		remove : function(node, keep_children) {
5236 			return this.run(node, function(node) {
5237 				var child, parent = node.parentNode;
5238 
5239 				if (!parent)
5240 					return null;
5241 
5242 				if (keep_children) {
5243 					while (child = node.firstChild) {
5244 						// IE 8 will crash if you don't remove completely empty text nodes
5245 						if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5246 							parent.insertBefore(child, node);
5247 						else
5248 							node.removeChild(child);
5249 					}
5250 				}
5251 
5252 				return parent.removeChild(node);
5253 			});
5254 		},
5255 
5256 		setStyle : function(n, na, v) {
5257 			var t = this;
5258 
5259 			return t.run(n, function(e) {
5260 				var s, i;
5261 
5262 				s = e.style;
5263 
5264 				// Camelcase it, if needed
5265 				na = na.replace(/-(\D)/g, function(a, b){
5266 					return b.toUpperCase();
5267 				});
5268 
5269 				// Default px suffix on these
5270 				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5271 					v += 'px';
5272 
5273 				switch (na) {
5274 					case 'opacity':
5275 						// IE specific opacity
5276 						if (isIE) {
5277 							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5278 
5279 							if (!n.currentStyle || !n.currentStyle.hasLayout)
5280 								s.display = 'inline-block';
5281 						}
5282 
5283 						// Fix for older browsers
5284 						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5285 						break;
5286 
5287 					case 'float':
5288 						isIE ? s.styleFloat = v : s.cssFloat = v;
5289 						break;
5290 					
5291 					default:
5292 						s[na] = v || '';
5293 				}
5294 
5295 				// Force update of the style data
5296 				if (t.settings.update_styles)
5297 					t.setAttrib(e, 'data-mce-style');
5298 			});
5299 		},
5300 
5301 		getStyle : function(n, na, c) {
5302 			n = this.get(n);
5303 
5304 			if (!n)
5305 				return;
5306 
5307 			// Gecko
5308 			if (this.doc.defaultView && c) {
5309 				// Remove camelcase
5310 				na = na.replace(/[A-Z]/g, function(a){
5311 					return '-' + a;
5312 				});
5313 
5314 				try {
5315 					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5316 				} catch (ex) {
5317 					// Old safari might fail
5318 					return null;
5319 				}
5320 			}
5321 
5322 			// Camelcase it, if needed
5323 			na = na.replace(/-(\D)/g, function(a, b){
5324 				return b.toUpperCase();
5325 			});
5326 
5327 			if (na == 'float')
5328 				na = isIE ? 'styleFloat' : 'cssFloat';
5329 
5330 			// IE & Opera
5331 			if (n.currentStyle && c)
5332 				return n.currentStyle[na];
5333 
5334 			return n.style ? n.style[na] : undefined;
5335 		},
5336 
5337 		setStyles : function(e, o) {
5338 			var t = this, s = t.settings, ol;
5339 
5340 			ol = s.update_styles;
5341 			s.update_styles = 0;
5342 
5343 			each(o, function(v, n) {
5344 				t.setStyle(e, n, v);
5345 			});
5346 
5347 			// Update style info
5348 			s.update_styles = ol;
5349 			if (s.update_styles)
5350 				t.setAttrib(e, s.cssText);
5351 		},
5352 
5353 		removeAllAttribs: function(e) {
5354 			return this.run(e, function(e) {
5355 				var i, attrs = e.attributes;
5356 				for (i = attrs.length - 1; i >= 0; i--) {
5357 					e.removeAttributeNode(attrs.item(i));
5358 				}
5359 			});
5360 		},
5361 
5362 		setAttrib : function(e, n, v) {
5363 			var t = this;
5364 
5365 			// Whats the point
5366 			if (!e || !n)
5367 				return;
5368 
5369 			// Strict XML mode
5370 			if (t.settings.strict)
5371 				n = n.toLowerCase();
5372 
5373 			return this.run(e, function(e) {
5374 				var s = t.settings;
5375 				var originalValue = e.getAttribute(n);
5376 				if (v !== null) {
5377 					switch (n) {
5378 						case "style":
5379 							if (!is(v, 'string')) {
5380 								each(v, function(v, n) {
5381 									t.setStyle(e, n, v);
5382 								});
5383 
5384 								return;
5385 							}
5386 
5387 							// No mce_style for elements with these since they might get resized by the user
5388 							if (s.keep_values) {
5389 								if (v && !t._isRes(v))
5390 									e.setAttribute('data-mce-style', v, 2);
5391 								else
5392 									e.removeAttribute('data-mce-style', 2);
5393 							}
5394 
5395 							e.style.cssText = v;
5396 							break;
5397 
5398 						case "class":
5399 							e.className = v || ''; // Fix IE null bug
5400 							break;
5401 
5402 						case "src":
5403 						case "href":
5404 							if (s.keep_values) {
5405 								if (s.url_converter)
5406 									v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5407 
5408 								t.setAttrib(e, 'data-mce-' + n, v, 2);
5409 							}
5410 
5411 							break;
5412 
5413 						case "shape":
5414 							e.setAttribute('data-mce-style', v);
5415 							break;
5416 					}
5417 				}
5418 				if (is(v) && v !== null && v.length !== 0)
5419 					e.setAttribute(n, '' + v, 2);
5420 				else
5421 					e.removeAttribute(n, 2);
5422 
5423 				// fire onChangeAttrib event for attributes that have changed
5424 				if (tinyMCE.activeEditor && originalValue != v) {
5425 					var ed = tinyMCE.activeEditor;
5426 					ed.onSetAttrib.dispatch(ed, e, n, v);
5427 				}
5428 			});
5429 		},
5430 
5431 		setAttribs : function(e, o) {
5432 			var t = this;
5433 
5434 			return this.run(e, function(e) {
5435 				each(o, function(v, n) {
5436 					t.setAttrib(e, n, v);
5437 				});
5438 			});
5439 		},
5440 
5441 		getAttrib : function(e, n, dv) {
5442 			var v, t = this, undef;
5443 
5444 			e = t.get(e);
5445 
5446 			if (!e || e.nodeType !== 1)
5447 				return dv === undef ? false : dv;
5448 
5449 			if (!is(dv))
5450 				dv = '';
5451 
5452 			// Try the mce variant for these
5453 			if (/^(src|href|style|coords|shape)$/.test(n)) {
5454 				v = e.getAttribute("data-mce-" + n);
5455 
5456 				if (v)
5457 					return v;
5458 			}
5459 
5460 			if (isIE && t.props[n]) {
5461 				v = e[t.props[n]];
5462 				v = v && v.nodeValue ? v.nodeValue : v;
5463 			}
5464 
5465 			if (!v)
5466 				v = e.getAttribute(n, 2);
5467 
5468 			// Check boolean attribs
5469 			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5470 				if (e[t.props[n]] === true && v === '')
5471 					return n;
5472 
5473 				return v ? n : '';
5474 			}
5475 
5476 			// Inner input elements will override attributes on form elements
5477 			if (e.nodeName === "FORM" && e.getAttributeNode(n))
5478 				return e.getAttributeNode(n).nodeValue;
5479 
5480 			if (n === 'style') {
5481 				v = v || e.style.cssText;
5482 
5483 				if (v) {
5484 					v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5485 
5486 					if (t.settings.keep_values && !t._isRes(v))
5487 						e.setAttribute('data-mce-style', v);
5488 				}
5489 			}
5490 
5491 			// Remove Apple and WebKit stuff
5492 			if (isWebKit && n === "class" && v)
5493 				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5494 
5495 			// Handle IE issues
5496 			if (isIE) {
5497 				switch (n) {
5498 					case 'rowspan':
5499 					case 'colspan':
5500 						// IE returns 1 as default value
5501 						if (v === 1)
5502 							v = '';
5503 
5504 						break;
5505 
5506 					case 'size':
5507 						// IE returns +0 as default value for size
5508 						if (v === '+0' || v === 20 || v === 0)
5509 							v = '';
5510 
5511 						break;
5512 
5513 					case 'width':
5514 					case 'height':
5515 					case 'vspace':
5516 					case 'checked':
5517 					case 'disabled':
5518 					case 'readonly':
5519 						if (v === 0)
5520 							v = '';
5521 
5522 						break;
5523 
5524 					case 'hspace':
5525 						// IE returns -1 as default value
5526 						if (v === -1)
5527 							v = '';
5528 
5529 						break;
5530 
5531 					case 'maxlength':
5532 					case 'tabindex':
5533 						// IE returns default value
5534 						if (v === 32768 || v === 2147483647 || v === '32768')
5535 							v = '';
5536 
5537 						break;
5538 
5539 					case 'multiple':
5540 					case 'compact':
5541 					case 'noshade':
5542 					case 'nowrap':
5543 						if (v === 65535)
5544 							return n;
5545 
5546 						return dv;
5547 
5548 					case 'shape':
5549 						v = v.toLowerCase();
5550 						break;
5551 
5552 					default:
5553 						// IE has odd anonymous function for event attributes
5554 						if (n.indexOf('on') === 0 && v)
5555 							v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5556 				}
5557 			}
5558 
5559 			return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5560 		},
5561 
5562 		getPos : function(n, ro) {
5563 			var t = this, x = 0, y = 0, e, d = t.doc, r;
5564 
5565 			n = t.get(n);
5566 			ro = ro || d.body;
5567 
5568 			if (n) {
5569 				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5570 				if (n.getBoundingClientRect) {
5571 					n = n.getBoundingClientRect();
5572 					e = t.boxModel ? d.documentElement : d.body;
5573 
5574 					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5575 					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5576 					x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5577 					y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5578 
5579 					return {x : x, y : y};
5580 				}
5581 
5582 				r = n;
5583 				while (r && r != ro && r.nodeType) {
5584 					x += r.offsetLeft || 0;
5585 					y += r.offsetTop || 0;
5586 					r = r.offsetParent;
5587 				}
5588 
5589 				r = n.parentNode;
5590 				while (r && r != ro && r.nodeType) {
5591 					x -= r.scrollLeft || 0;
5592 					y -= r.scrollTop || 0;
5593 					r = r.parentNode;
5594 				}
5595 			}
5596 
5597 			return {x : x, y : y};
5598 		},
5599 
5600 		parseStyle : function(st) {
5601 			return this.styles.parse(st);
5602 		},
5603 
5604 		serializeStyle : function(o, name) {
5605 			return this.styles.serialize(o, name);
5606 		},
5607 
5608 		addStyle: function(cssText) {
5609 			var doc = this.doc, head;
5610 
5611 			// Create style element if needed
5612 			styleElm = doc.getElementById('mceDefaultStyles');
5613 			if (!styleElm) {
5614 				styleElm = doc.createElement('style'),
5615 				styleElm.id = 'mceDefaultStyles';
5616 				styleElm.type = 'text/css';
5617 
5618 				head = doc.getElementsByTagName('head')[0]
5619 				if (head.firstChild) {
5620 					head.insertBefore(styleElm, head.firstChild);
5621 				} else {
5622 					head.appendChild(styleElm);
5623 				}
5624 			}
5625 
5626 			// Append style data to old or new style element
5627 			if (styleElm.styleSheet) {
5628 				styleElm.styleSheet.cssText += cssText;
5629 			} else {
5630 				styleElm.appendChild(doc.createTextNode(cssText));
5631 			}
5632 		},
5633 
5634 		loadCSS : function(u) {
5635 			var t = this, d = t.doc, head;
5636 
5637 			if (!u)
5638 				u = '';
5639 
5640 			head = d.getElementsByTagName('head')[0];
5641 
5642 			each(u.split(','), function(u) {
5643 				var link;
5644 
5645 				if (t.files[u])
5646 					return;
5647 
5648 				t.files[u] = true;
5649 				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5650 
5651 				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5652 				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5653 				// It's ugly but it seems to work fine.
5654 				if (isIE && d.documentMode && d.recalc) {
5655 					link.onload = function() {
5656 						if (d.recalc)
5657 							d.recalc();
5658 
5659 						link.onload = null;
5660 					};
5661 				}
5662 
5663 				head.appendChild(link);
5664 			});
5665 		},
5666 
5667 		addClass : function(e, c) {
5668 			return this.run(e, function(e) {
5669 				var o;
5670 
5671 				if (!c)
5672 					return 0;
5673 
5674 				if (this.hasClass(e, c))
5675 					return e.className;
5676 
5677 				o = this.removeClass(e, c);
5678 
5679 				return e.className = (o != '' ? (o + ' ') : '') + c;
5680 			});
5681 		},
5682 
5683 		removeClass : function(e, c) {
5684 			var t = this, re;
5685 
5686 			return t.run(e, function(e) {
5687 				var v;
5688 
5689 				if (t.hasClass(e, c)) {
5690 					if (!re)
5691 						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5692 
5693 					v = e.className.replace(re, ' ');
5694 					v = tinymce.trim(v != ' ' ? v : '');
5695 
5696 					e.className = v;
5697 
5698 					// Empty class attr
5699 					if (!v) {
5700 						e.removeAttribute('class');
5701 						e.removeAttribute('className');
5702 					}
5703 
5704 					return v;
5705 				}
5706 
5707 				return e.className;
5708 			});
5709 		},
5710 
5711 		hasClass : function(n, c) {
5712 			n = this.get(n);
5713 
5714 			if (!n || !c)
5715 				return false;
5716 
5717 			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
5718 		},
5719 
5720 		show : function(e) {
5721 			return this.setStyle(e, 'display', 'block');
5722 		},
5723 
5724 		hide : function(e) {
5725 			return this.setStyle(e, 'display', 'none');
5726 		},
5727 
5728 		isHidden : function(e) {
5729 			e = this.get(e);
5730 
5731 			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
5732 		},
5733 
5734 		uniqueId : function(p) {
5735 			return (!p ? 'mce_' : p) + (this.counter++);
5736 		},
5737 
5738 		setHTML : function(element, html) {
5739 			var self = this;
5740 
5741 			return self.run(element, function(element) {
5742 				if (isIE) {
5743 					// Remove all child nodes, IE keeps empty text nodes in DOM
5744 					while (element.firstChild)
5745 						element.removeChild(element.firstChild);
5746 
5747 					try {
5748 						// IE will remove comments from the beginning
5749 						// unless you padd the contents with something
5750 						element.innerHTML = '<br />' + html;
5751 						element.removeChild(element.firstChild);
5752 					} catch (ex) {
5753 						// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
5754 						// This seems to fix this problem
5755 
5756 						// Create new div with HTML contents and a BR infront to keep comments
5757 						var newElement = self.create('div');
5758 						newElement.innerHTML = '<br />' + html;
5759 
5760 						// Add all children from div to target
5761 						each (tinymce.grep(newElement.childNodes), function(node, i) {
5762 							// Skip br element
5763 							if (i && element.canHaveHTML)
5764 								element.appendChild(node);
5765 						});
5766 					}
5767 				} else
5768 					element.innerHTML = html;
5769 
5770 				return html;
5771 			});
5772 		},
5773 
5774 		getOuterHTML : function(elm) {
5775 			var doc, self = this;
5776 
5777 			elm = self.get(elm);
5778 
5779 			if (!elm)
5780 				return null;
5781 
5782 			if (elm.nodeType === 1 && self.hasOuterHTML)
5783 				return elm.outerHTML;
5784 
5785 			doc = (elm.ownerDocument || self.doc).createElement("body");
5786 			doc.appendChild(elm.cloneNode(true));
5787 
5788 			return doc.innerHTML;
5789 		},
5790 
5791 		setOuterHTML : function(e, h, d) {
5792 			var t = this;
5793 
5794 			function setHTML(e, h, d) {
5795 				var n, tp;
5796 
5797 				tp = d.createElement("body");
5798 				tp.innerHTML = h;
5799 
5800 				n = tp.lastChild;
5801 				while (n) {
5802 					t.insertAfter(n.cloneNode(true), e);
5803 					n = n.previousSibling;
5804 				}
5805 
5806 				t.remove(e);
5807 			};
5808 
5809 			return this.run(e, function(e) {
5810 				e = t.get(e);
5811 
5812 				// Only set HTML on elements
5813 				if (e.nodeType == 1) {
5814 					d = d || e.ownerDocument || t.doc;
5815 
5816 					if (isIE) {
5817 						try {
5818 							// Try outerHTML for IE it sometimes produces an unknown runtime error
5819 							if (isIE && e.nodeType == 1)
5820 								e.outerHTML = h;
5821 							else
5822 								setHTML(e, h, d);
5823 						} catch (ex) {
5824 							// Fix for unknown runtime error
5825 							setHTML(e, h, d);
5826 						}
5827 					} else
5828 						setHTML(e, h, d);
5829 				}
5830 			});
5831 		},
5832 
5833 		decode : Entities.decode,
5834 
5835 		encode : Entities.encodeAllRaw,
5836 
5837 		insertAfter : function(node, reference_node) {
5838 			reference_node = this.get(reference_node);
5839 
5840 			return this.run(node, function(node) {
5841 				var parent, nextSibling;
5842 
5843 				parent = reference_node.parentNode;
5844 				nextSibling = reference_node.nextSibling;
5845 
5846 				if (nextSibling)
5847 					parent.insertBefore(node, nextSibling);
5848 				else
5849 					parent.appendChild(node);
5850 
5851 				return node;
5852 			});
5853 		},
5854 
5855 		replace : function(n, o, k) {
5856 			var t = this;
5857 
5858 			if (is(o, 'array'))
5859 				n = n.cloneNode(true);
5860 
5861 			return t.run(o, function(o) {
5862 				if (k) {
5863 					each(tinymce.grep(o.childNodes), function(c) {
5864 						n.appendChild(c);
5865 					});
5866 				}
5867 
5868 				return o.parentNode.replaceChild(n, o);
5869 			});
5870 		},
5871 
5872 		rename : function(elm, name) {
5873 			var t = this, newElm;
5874 
5875 			if (elm.nodeName != name.toUpperCase()) {
5876 				// Rename block element
5877 				newElm = t.create(name);
5878 
5879 				// Copy attribs to new block
5880 				each(t.getAttribs(elm), function(attr_node) {
5881 					t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
5882 				});
5883 
5884 				// Replace block
5885 				t.replace(newElm, elm, 1);
5886 			}
5887 
5888 			return newElm || elm;
5889 		},
5890 
5891 		findCommonAncestor : function(a, b) {
5892 			var ps = a, pe;
5893 
5894 			while (ps) {
5895 				pe = b;
5896 
5897 				while (pe && ps != pe)
5898 					pe = pe.parentNode;
5899 
5900 				if (ps == pe)
5901 					break;
5902 
5903 				ps = ps.parentNode;
5904 			}
5905 
5906 			if (!ps && a.ownerDocument)
5907 				return a.ownerDocument.documentElement;
5908 
5909 			return ps;
5910 		},
5911 
5912 		toHex : function(s) {
5913 			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
5914 
5915 			function hex(s) {
5916 				s = parseInt(s, 10).toString(16);
5917 
5918 				return s.length > 1 ? s : '0' + s; // 0 -> 00
5919 			};
5920 
5921 			if (c) {
5922 				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
5923 
5924 				return s;
5925 			}
5926 
5927 			return s;
5928 		},
5929 
5930 		getClasses : function() {
5931 			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
5932 
5933 			if (t.classes)
5934 				return t.classes;
5935 
5936 			function addClasses(s) {
5937 				// IE style imports
5938 				each(s.imports, function(r) {
5939 					addClasses(r);
5940 				});
5941 
5942 				each(s.cssRules || s.rules, function(r) {
5943 					// Real type or fake it on IE
5944 					switch (r.type || 1) {
5945 						// Rule
5946 						case 1:
5947 							if (r.selectorText) {
5948 								each(r.selectorText.split(','), function(v) {
5949 									v = v.replace(/^\s*|\s*$|^\s\./g, "");
5950 
5951 									// Is internal or it doesn't contain a class
5952 									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
5953 										return;
5954 
5955 									// Remove everything but class name
5956 									ov = v;
5957 									v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
5958 
5959 									// Filter classes
5960 									if (f && !(v = f(v, ov)))
5961 										return;
5962 
5963 									if (!lo[v]) {
5964 										cl.push({'class' : v});
5965 										lo[v] = 1;
5966 									}
5967 								});
5968 							}
5969 							break;
5970 
5971 						// Import
5972 						case 3:
5973 							addClasses(r.styleSheet);
5974 							break;
5975 					}
5976 				});
5977 			};
5978 
5979 			try {
5980 				each(t.doc.styleSheets, addClasses);
5981 			} catch (ex) {
5982 				// Ignore
5983 			}
5984 
5985 			if (cl.length > 0)
5986 				t.classes = cl;
5987 
5988 			return cl;
5989 		},
5990 
5991 		run : function(e, f, s) {
5992 			var t = this, o;
5993 
5994 			if (t.doc && typeof(e) === 'string')
5995 				e = t.get(e);
5996 
5997 			if (!e)
5998 				return false;
5999 
6000 			s = s || this;
6001 			if (!e.nodeType && (e.length || e.length === 0)) {
6002 				o = [];
6003 
6004 				each(e, function(e, i) {
6005 					if (e) {
6006 						if (typeof(e) == 'string')
6007 							e = t.doc.getElementById(e);
6008 
6009 						o.push(f.call(s, e, i));
6010 					}
6011 				});
6012 
6013 				return o;
6014 			}
6015 
6016 			return f.call(s, e);
6017 		},
6018 
6019 		getAttribs : function(n) {
6020 			var o;
6021 
6022 			n = this.get(n);
6023 
6024 			if (!n)
6025 				return [];
6026 
6027 			if (isIE) {
6028 				o = [];
6029 
6030 				// Object will throw exception in IE
6031 				if (n.nodeName == 'OBJECT')
6032 					return n.attributes;
6033 
6034 				// IE doesn't keep the selected attribute if you clone option elements
6035 				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6036 					o.push({specified : 1, nodeName : 'selected'});
6037 
6038 				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
6039 				n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6040 					o.push({specified : 1, nodeName : a});
6041 				});
6042 
6043 				return o;
6044 			}
6045 
6046 			return n.attributes;
6047 		},
6048 
6049 		isEmpty : function(node, elements) {
6050 			var self = this, i, attributes, type, walker, name, brCount = 0;
6051 
6052 			node = node.firstChild;
6053 			if (node) {
6054 				walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6055 				elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6056 
6057 				do {
6058 					type = node.nodeType;
6059 
6060 					if (type === 1) {
6061 						// Ignore bogus elements
6062 						if (node.getAttribute('data-mce-bogus'))
6063 							continue;
6064 
6065 						// Keep empty elements like <img />
6066 						name = node.nodeName.toLowerCase();
6067 						if (elements && elements[name]) {
6068 							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6069 							if (name === 'br') {
6070 								brCount++;
6071 								continue;
6072 							}
6073 
6074 							return false;
6075 						}
6076 
6077 						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6078 						attributes = self.getAttribs(node);
6079 						i = node.attributes.length;
6080 						while (i--) {
6081 							name = node.attributes[i].nodeName;
6082 							if (name === "name" || name === 'data-mce-bookmark')
6083 								return false;
6084 						}
6085 					}
6086 
6087 					// Keep comment nodes
6088 					if (type == 8)
6089 						return false;
6090 
6091 					// Keep non whitespace text nodes
6092 					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6093 						return false;
6094 				} while (node = walker.next());
6095 			}
6096 
6097 			return brCount <= 1;
6098 		},
6099 
6100 		destroy : function(s) {
6101 			var t = this;
6102 
6103 			t.win = t.doc = t.root = t.events = t.frag = null;
6104 
6105 			// Manual destroy then remove unload handler
6106 			if (!s)
6107 				tinymce.removeUnload(t.destroy);
6108 		},
6109 
6110 		createRng : function() {
6111 			var d = this.doc;
6112 
6113 			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6114 		},
6115 
6116 		nodeIndex : function(node, normalized) {
6117 			var idx = 0, lastNodeType, lastNode, nodeType;
6118 
6119 			if (node) {
6120 				for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6121 					nodeType = node.nodeType;
6122 
6123 					// Normalize text nodes
6124 					if (normalized && nodeType == 3) {
6125 						if (nodeType == lastNodeType || !node.nodeValue.length)
6126 							continue;
6127 					}
6128 					idx++;
6129 					lastNodeType = nodeType;
6130 				}
6131 			}
6132 
6133 			return idx;
6134 		},
6135 
6136 		split : function(pe, e, re) {
6137 			var t = this, r = t.createRng(), bef, aft, pa;
6138 
6139 			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6140 			// but we don't want that in our code since it serves no purpose for the end user
6141 			// For example if this is chopped:
6142 			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6143 			// would produce:
6144 			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6145 			// this function will then trim of empty edges and produce:
6146 			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6147 			function trim(node) {
6148 				var i, children = node.childNodes, type = node.nodeType;
6149 
6150 				function surroundedBySpans(node) {
6151 					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6152 					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6153 					return previousIsSpan && nextIsSpan;
6154 				}
6155 
6156 				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6157 					return;
6158 
6159 				for (i = children.length - 1; i >= 0; i--)
6160 					trim(children[i]);
6161 
6162 				if (type != 9) {
6163 					// Keep non whitespace text nodes
6164 					if (type == 3 && node.nodeValue.length > 0) {
6165 						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6166 						// Also keep text nodes with only spaces if surrounded by spans.
6167 						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6168 						var trimmedLength = tinymce.trim(node.nodeValue).length;
6169 						if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6170 							return;
6171 					} else if (type == 1) {
6172 						// If the only child is a bookmark then move it up
6173 						children = node.childNodes;
6174 						if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6175 							node.parentNode.insertBefore(children[0], node);
6176 
6177 						// Keep non empty elements or img, hr etc
6178 						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6179 							return;
6180 					}
6181 
6182 					t.remove(node);
6183 				}
6184 
6185 				return node;
6186 			};
6187 
6188 			if (pe && e) {
6189 				// Get before chunk
6190 				r.setStart(pe.parentNode, t.nodeIndex(pe));
6191 				r.setEnd(e.parentNode, t.nodeIndex(e));
6192 				bef = r.extractContents();
6193 
6194 				// Get after chunk
6195 				r = t.createRng();
6196 				r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6197 				r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6198 				aft = r.extractContents();
6199 
6200 				// Insert before chunk
6201 				pa = pe.parentNode;
6202 				pa.insertBefore(trim(bef), pe);
6203 
6204 				// Insert middle chunk
6205 				if (re)
6206 				pa.replaceChild(re, e);
6207 			else
6208 				pa.insertBefore(e, pe);
6209 
6210 				// Insert after chunk
6211 				pa.insertBefore(trim(aft), pe);
6212 				t.remove(pe);
6213 
6214 				return re || e;
6215 			}
6216 		},
6217 
6218 		bind : function(target, name, func, scope) {
6219 			return this.events.add(target, name, func, scope || this);
6220 		},
6221 
6222 		unbind : function(target, name, func) {
6223 			return this.events.remove(target, name, func);
6224 		},
6225 
6226 		fire : function(target, name, evt) {
6227 			return this.events.fire(target, name, evt);
6228 		},
6229 
6230 		// Returns the content editable state of a node
6231 		getContentEditable: function(node) {
6232 			var contentEditable;
6233 
6234 			// Check type
6235 			if (node.nodeType != 1) {
6236 				return null;
6237 			}
6238 
6239 			// Check for fake content editable
6240 			contentEditable = node.getAttribute("data-mce-contenteditable");
6241 			if (contentEditable && contentEditable !== "inherit") {
6242 				return contentEditable;
6243 			}
6244 
6245 			// Check for real content editable
6246 			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6247 		},
6248 
6249 
6250 		_findSib : function(node, selector, name) {
6251 			var t = this, f = selector;
6252 
6253 			if (node) {
6254 				// If expression make a function of it using is
6255 				if (is(f, 'string')) {
6256 					f = function(node) {
6257 						return t.is(node, selector);
6258 					};
6259 				}
6260 
6261 				// Loop all siblings
6262 				for (node = node[name]; node; node = node[name]) {
6263 					if (f(node))
6264 						return node;
6265 				}
6266 			}
6267 
6268 			return null;
6269 		},
6270 
6271 		_isRes : function(c) {
6272 			// Is live resizble element
6273 			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6274 		}
6275 
6276 		/*
6277 		walk : function(n, f, s) {
6278 			var d = this.doc, w;
6279 
6280 			if (d.createTreeWalker) {
6281 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6282 
6283 				while ((n = w.nextNode()) != null)
6284 					f.call(s || this, n);
6285 			} else
6286 				tinymce.walk(n, f, 'childNodes', s);
6287 		}
6288 		*/
6289 
6290 		/*
6291 		toRGB : function(s) {
6292 			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6293 
6294 			if (c) {
6295 				// #FFF -> #FFFFFF
6296 				if (!is(c[3]))
6297 					c[3] = c[2] = c[1];
6298 
6299 				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6300 			}
6301 
6302 			return s;
6303 		}
6304 		*/
6305 	});
6306 
6307 	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6308 })(tinymce);
6309 
6310 (function(ns) {
6311 	// Range constructor
6312 	function Range(dom) {
6313 		var t = this,
6314 			doc = dom.doc,
6315 			EXTRACT = 0,
6316 			CLONE = 1,
6317 			DELETE = 2,
6318 			TRUE = true,
6319 			FALSE = false,
6320 			START_OFFSET = 'startOffset',
6321 			START_CONTAINER = 'startContainer',
6322 			END_CONTAINER = 'endContainer',
6323 			END_OFFSET = 'endOffset',
6324 			extend = tinymce.extend,
6325 			nodeIndex = dom.nodeIndex;
6326 
6327 		extend(t, {
6328 			// Inital states
6329 			startContainer : doc,
6330 			startOffset : 0,
6331 			endContainer : doc,
6332 			endOffset : 0,
6333 			collapsed : TRUE,
6334 			commonAncestorContainer : doc,
6335 
6336 			// Range constants
6337 			START_TO_START : 0,
6338 			START_TO_END : 1,
6339 			END_TO_END : 2,
6340 			END_TO_START : 3,
6341 
6342 			// Public methods
6343 			setStart : setStart,
6344 			setEnd : setEnd,
6345 			setStartBefore : setStartBefore,
6346 			setStartAfter : setStartAfter,
6347 			setEndBefore : setEndBefore,
6348 			setEndAfter : setEndAfter,
6349 			collapse : collapse,
6350 			selectNode : selectNode,
6351 			selectNodeContents : selectNodeContents,
6352 			compareBoundaryPoints : compareBoundaryPoints,
6353 			deleteContents : deleteContents,
6354 			extractContents : extractContents,
6355 			cloneContents : cloneContents,
6356 			insertNode : insertNode,
6357 			surroundContents : surroundContents,
6358 			cloneRange : cloneRange,
6359 			toStringIE : toStringIE
6360 		});
6361 
6362 		function createDocumentFragment() {
6363 			return doc.createDocumentFragment();
6364 		};
6365 
6366 		function setStart(n, o) {
6367 			_setEndPoint(TRUE, n, o);
6368 		};
6369 
6370 		function setEnd(n, o) {
6371 			_setEndPoint(FALSE, n, o);
6372 		};
6373 
6374 		function setStartBefore(n) {
6375 			setStart(n.parentNode, nodeIndex(n));
6376 		};
6377 
6378 		function setStartAfter(n) {
6379 			setStart(n.parentNode, nodeIndex(n) + 1);
6380 		};
6381 
6382 		function setEndBefore(n) {
6383 			setEnd(n.parentNode, nodeIndex(n));
6384 		};
6385 
6386 		function setEndAfter(n) {
6387 			setEnd(n.parentNode, nodeIndex(n) + 1);
6388 		};
6389 
6390 		function collapse(ts) {
6391 			if (ts) {
6392 				t[END_CONTAINER] = t[START_CONTAINER];
6393 				t[END_OFFSET] = t[START_OFFSET];
6394 			} else {
6395 				t[START_CONTAINER] = t[END_CONTAINER];
6396 				t[START_OFFSET] = t[END_OFFSET];
6397 			}
6398 
6399 			t.collapsed = TRUE;
6400 		};
6401 
6402 		function selectNode(n) {
6403 			setStartBefore(n);
6404 			setEndAfter(n);
6405 		};
6406 
6407 		function selectNodeContents(n) {
6408 			setStart(n, 0);
6409 			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6410 		};
6411 
6412 		function compareBoundaryPoints(h, r) {
6413 			var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6414 			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6415 
6416 			// Check START_TO_START
6417 			if (h === 0)
6418 				return _compareBoundaryPoints(sc, so, rsc, rso);
6419 	
6420 			// Check START_TO_END
6421 			if (h === 1)
6422 				return _compareBoundaryPoints(ec, eo, rsc, rso);
6423 	
6424 			// Check END_TO_END
6425 			if (h === 2)
6426 				return _compareBoundaryPoints(ec, eo, rec, reo);
6427 	
6428 			// Check END_TO_START
6429 			if (h === 3) 
6430 				return _compareBoundaryPoints(sc, so, rec, reo);
6431 		};
6432 
6433 		function deleteContents() {
6434 			_traverse(DELETE);
6435 		};
6436 
6437 		function extractContents() {
6438 			return _traverse(EXTRACT);
6439 		};
6440 
6441 		function cloneContents() {
6442 			return _traverse(CLONE);
6443 		};
6444 
6445 		function insertNode(n) {
6446 			var startContainer = this[START_CONTAINER],
6447 				startOffset = this[START_OFFSET], nn, o;
6448 
6449 			// Node is TEXT_NODE or CDATA
6450 			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6451 				if (!startOffset) {
6452 					// At the start of text
6453 					startContainer.parentNode.insertBefore(n, startContainer);
6454 				} else if (startOffset >= startContainer.nodeValue.length) {
6455 					// At the end of text
6456 					dom.insertAfter(n, startContainer);
6457 				} else {
6458 					// Middle, need to split
6459 					nn = startContainer.splitText(startOffset);
6460 					startContainer.parentNode.insertBefore(n, nn);
6461 				}
6462 			} else {
6463 				// Insert element node
6464 				if (startContainer.childNodes.length > 0)
6465 					o = startContainer.childNodes[startOffset];
6466 
6467 				if (o)
6468 					startContainer.insertBefore(n, o);
6469 				else
6470 					startContainer.appendChild(n);
6471 			}
6472 		};
6473 
6474 		function surroundContents(n) {
6475 			var f = t.extractContents();
6476 
6477 			t.insertNode(n);
6478 			n.appendChild(f);
6479 			t.selectNode(n);
6480 		};
6481 
6482 		function cloneRange() {
6483 			return extend(new Range(dom), {
6484 				startContainer : t[START_CONTAINER],
6485 				startOffset : t[START_OFFSET],
6486 				endContainer : t[END_CONTAINER],
6487 				endOffset : t[END_OFFSET],
6488 				collapsed : t.collapsed,
6489 				commonAncestorContainer : t.commonAncestorContainer
6490 			});
6491 		};
6492 
6493 		// Private methods
6494 
6495 		function _getSelectedNode(container, offset) {
6496 			var child;
6497 
6498 			if (container.nodeType == 3 /* TEXT_NODE */)
6499 				return container;
6500 
6501 			if (offset < 0)
6502 				return container;
6503 
6504 			child = container.firstChild;
6505 			while (child && offset > 0) {
6506 				--offset;
6507 				child = child.nextSibling;
6508 			}
6509 
6510 			if (child)
6511 				return child;
6512 
6513 			return container;
6514 		};
6515 
6516 		function _isCollapsed() {
6517 			return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6518 		};
6519 
6520 		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6521 			var c, offsetC, n, cmnRoot, childA, childB;
6522 			
6523 			// In the first case the boundary-points have the same container. A is before B
6524 			// if its offset is less than the offset of B, A is equal to B if its offset is
6525 			// equal to the offset of B, and A is after B if its offset is greater than the
6526 			// offset of B.
6527 			if (containerA == containerB) {
6528 				if (offsetA == offsetB)
6529 					return 0; // equal
6530 
6531 				if (offsetA < offsetB)
6532 					return -1; // before
6533 
6534 				return 1; // after
6535 			}
6536 
6537 			// In the second case a child node C of the container of A is an ancestor
6538 			// container of B. In this case, A is before B if the offset of A is less than or
6539 			// equal to the index of the child node C and A is after B otherwise.
6540 			c = containerB;
6541 			while (c && c.parentNode != containerA)
6542 				c = c.parentNode;
6543 
6544 			if (c) {
6545 				offsetC = 0;
6546 				n = containerA.firstChild;
6547 
6548 				while (n != c && offsetC < offsetA) {
6549 					offsetC++;
6550 					n = n.nextSibling;
6551 				}
6552 
6553 				if (offsetA <= offsetC)
6554 					return -1; // before
6555 
6556 				return 1; // after
6557 			}
6558 
6559 			// In the third case a child node C of the container of B is an ancestor container
6560 			// of A. In this case, A is before B if the index of the child node C is less than
6561 			// the offset of B and A is after B otherwise.
6562 			c = containerA;
6563 			while (c && c.parentNode != containerB) {
6564 				c = c.parentNode;
6565 			}
6566 
6567 			if (c) {
6568 				offsetC = 0;
6569 				n = containerB.firstChild;
6570 
6571 				while (n != c && offsetC < offsetB) {
6572 					offsetC++;
6573 					n = n.nextSibling;
6574 				}
6575 
6576 				if (offsetC < offsetB)
6577 					return -1; // before
6578 
6579 				return 1; // after
6580 			}
6581 
6582 			// In the fourth case, none of three other cases hold: the containers of A and B
6583 			// are siblings or descendants of sibling nodes. In this case, A is before B if
6584 			// the container of A is before the container of B in a pre-order traversal of the
6585 			// Ranges' context tree and A is after B otherwise.
6586 			cmnRoot = dom.findCommonAncestor(containerA, containerB);
6587 			childA = containerA;
6588 
6589 			while (childA && childA.parentNode != cmnRoot)
6590 				childA = childA.parentNode;
6591 
6592 			if (!childA)
6593 				childA = cmnRoot;
6594 
6595 			childB = containerB;
6596 			while (childB && childB.parentNode != cmnRoot)
6597 				childB = childB.parentNode;
6598 
6599 			if (!childB)
6600 				childB = cmnRoot;
6601 
6602 			if (childA == childB)
6603 				return 0; // equal
6604 
6605 			n = cmnRoot.firstChild;
6606 			while (n) {
6607 				if (n == childA)
6608 					return -1; // before
6609 
6610 				if (n == childB)
6611 					return 1; // after
6612 
6613 				n = n.nextSibling;
6614 			}
6615 		};
6616 
6617 		function _setEndPoint(st, n, o) {
6618 			var ec, sc;
6619 
6620 			if (st) {
6621 				t[START_CONTAINER] = n;
6622 				t[START_OFFSET] = o;
6623 			} else {
6624 				t[END_CONTAINER] = n;
6625 				t[END_OFFSET] = o;
6626 			}
6627 
6628 			// If one boundary-point of a Range is set to have a root container
6629 			// other than the current one for the Range, the Range is collapsed to
6630 			// the new position. This enforces the restriction that both boundary-
6631 			// points of a Range must have the same root container.
6632 			ec = t[END_CONTAINER];
6633 			while (ec.parentNode)
6634 				ec = ec.parentNode;
6635 
6636 			sc = t[START_CONTAINER];
6637 			while (sc.parentNode)
6638 				sc = sc.parentNode;
6639 
6640 			if (sc == ec) {
6641 				// The start position of a Range is guaranteed to never be after the
6642 				// end position. To enforce this restriction, if the start is set to
6643 				// be at a position after the end, the Range is collapsed to that
6644 				// position.
6645 				if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6646 					t.collapse(st);
6647 			} else
6648 				t.collapse(st);
6649 
6650 			t.collapsed = _isCollapsed();
6651 			t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6652 		};
6653 
6654 		function _traverse(how) {
6655 			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6656 
6657 			if (t[START_CONTAINER] == t[END_CONTAINER])
6658 				return _traverseSameContainer(how);
6659 
6660 			for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6661 				if (p == t[START_CONTAINER])
6662 					return _traverseCommonStartContainer(c, how);
6663 
6664 				++endContainerDepth;
6665 			}
6666 
6667 			for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6668 				if (p == t[END_CONTAINER])
6669 					return _traverseCommonEndContainer(c, how);
6670 
6671 				++startContainerDepth;
6672 			}
6673 
6674 			depthDiff = startContainerDepth - endContainerDepth;
6675 
6676 			startNode = t[START_CONTAINER];
6677 			while (depthDiff > 0) {
6678 				startNode = startNode.parentNode;
6679 				depthDiff--;
6680 			}
6681 
6682 			endNode = t[END_CONTAINER];
6683 			while (depthDiff < 0) {
6684 				endNode = endNode.parentNode;
6685 				depthDiff++;
6686 			}
6687 
6688 			// ascend the ancestor hierarchy until we have a common parent.
6689 			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6690 				startNode = sp;
6691 				endNode = ep;
6692 			}
6693 
6694 			return _traverseCommonAncestors(startNode, endNode, how);
6695 		};
6696 
6697 		 function _traverseSameContainer(how) {
6698 			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
6699 
6700 			if (how != DELETE)
6701 				frag = createDocumentFragment();
6702 
6703 			// If selection is empty, just return the fragment
6704 			if (t[START_OFFSET] == t[END_OFFSET])
6705 				return frag;
6706 
6707 			// Text node needs special case handling
6708 			if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
6709 				// get the substring
6710 				s = t[START_CONTAINER].nodeValue;
6711 				sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
6712 
6713 				// set the original text node to its new value
6714 				if (how != CLONE) {
6715 					n = t[START_CONTAINER];
6716 					start = t[START_OFFSET];
6717 					len = t[END_OFFSET] - t[START_OFFSET];
6718 
6719 					if (start === 0 && len >= n.nodeValue.length - 1) {
6720 						n.parentNode.removeChild(n);
6721 					} else {
6722 						n.deleteData(start, len);
6723 					}
6724 
6725 					// Nothing is partially selected, so collapse to start point
6726 					t.collapse(TRUE);
6727 				}
6728 
6729 				if (how == DELETE)
6730 					return;
6731 
6732 				if (sub.length > 0) {
6733 					frag.appendChild(doc.createTextNode(sub));
6734 				}
6735 
6736 				return frag;
6737 			}
6738 
6739 			// Copy nodes between the start/end offsets.
6740 			n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
6741 			cnt = t[END_OFFSET] - t[START_OFFSET];
6742 
6743 			while (n && cnt > 0) {
6744 				sibling = n.nextSibling;
6745 				xferNode = _traverseFullySelected(n, how);
6746 
6747 				if (frag)
6748 					frag.appendChild( xferNode );
6749 
6750 				--cnt;
6751 				n = sibling;
6752 			}
6753 
6754 			// Nothing is partially selected, so collapse to start point
6755 			if (how != CLONE)
6756 				t.collapse(TRUE);
6757 
6758 			return frag;
6759 		};
6760 
6761 		function _traverseCommonStartContainer(endAncestor, how) {
6762 			var frag, n, endIdx, cnt, sibling, xferNode;
6763 
6764 			if (how != DELETE)
6765 				frag = createDocumentFragment();
6766 
6767 			n = _traverseRightBoundary(endAncestor, how);
6768 
6769 			if (frag)
6770 				frag.appendChild(n);
6771 
6772 			endIdx = nodeIndex(endAncestor);
6773 			cnt = endIdx - t[START_OFFSET];
6774 
6775 			if (cnt <= 0) {
6776 				// Collapse to just before the endAncestor, which
6777 				// is partially selected.
6778 				if (how != CLONE) {
6779 					t.setEndBefore(endAncestor);
6780 					t.collapse(FALSE);
6781 				}
6782 
6783 				return frag;
6784 			}
6785 
6786 			n = endAncestor.previousSibling;
6787 			while (cnt > 0) {
6788 				sibling = n.previousSibling;
6789 				xferNode = _traverseFullySelected(n, how);
6790 
6791 				if (frag)
6792 					frag.insertBefore(xferNode, frag.firstChild);
6793 
6794 				--cnt;
6795 				n = sibling;
6796 			}
6797 
6798 			// Collapse to just before the endAncestor, which
6799 			// is partially selected.
6800 			if (how != CLONE) {
6801 				t.setEndBefore(endAncestor);
6802 				t.collapse(FALSE);
6803 			}
6804 
6805 			return frag;
6806 		};
6807 
6808 		function _traverseCommonEndContainer(startAncestor, how) {
6809 			var frag, startIdx, n, cnt, sibling, xferNode;
6810 
6811 			if (how != DELETE)
6812 				frag = createDocumentFragment();
6813 
6814 			n = _traverseLeftBoundary(startAncestor, how);
6815 			if (frag)
6816 				frag.appendChild(n);
6817 
6818 			startIdx = nodeIndex(startAncestor);
6819 			++startIdx; // Because we already traversed it
6820 
6821 			cnt = t[END_OFFSET] - startIdx;
6822 			n = startAncestor.nextSibling;
6823 			while (n && cnt > 0) {
6824 				sibling = n.nextSibling;
6825 				xferNode = _traverseFullySelected(n, how);
6826 
6827 				if (frag)
6828 					frag.appendChild(xferNode);
6829 
6830 				--cnt;
6831 				n = sibling;
6832 			}
6833 
6834 			if (how != CLONE) {
6835 				t.setStartAfter(startAncestor);
6836 				t.collapse(TRUE);
6837 			}
6838 
6839 			return frag;
6840 		};
6841 
6842 		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
6843 			var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
6844 
6845 			if (how != DELETE)
6846 				frag = createDocumentFragment();
6847 
6848 			n = _traverseLeftBoundary(startAncestor, how);
6849 			if (frag)
6850 				frag.appendChild(n);
6851 
6852 			commonParent = startAncestor.parentNode;
6853 			startOffset = nodeIndex(startAncestor);
6854 			endOffset = nodeIndex(endAncestor);
6855 			++startOffset;
6856 
6857 			cnt = endOffset - startOffset;
6858 			sibling = startAncestor.nextSibling;
6859 
6860 			while (cnt > 0) {
6861 				nextSibling = sibling.nextSibling;
6862 				n = _traverseFullySelected(sibling, how);
6863 
6864 				if (frag)
6865 					frag.appendChild(n);
6866 
6867 				sibling = nextSibling;
6868 				--cnt;
6869 			}
6870 
6871 			n = _traverseRightBoundary(endAncestor, how);
6872 
6873 			if (frag)
6874 				frag.appendChild(n);
6875 
6876 			if (how != CLONE) {
6877 				t.setStartAfter(startAncestor);
6878 				t.collapse(TRUE);
6879 			}
6880 
6881 			return frag;
6882 		};
6883 
6884 		function _traverseRightBoundary(root, how) {
6885 			var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
6886 
6887 			if (next == root)
6888 				return _traverseNode(next, isFullySelected, FALSE, how);
6889 
6890 			parent = next.parentNode;
6891 			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
6892 
6893 			while (parent) {
6894 				while (next) {
6895 					prevSibling = next.previousSibling;
6896 					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
6897 
6898 					if (how != DELETE)
6899 						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
6900 
6901 					isFullySelected = TRUE;
6902 					next = prevSibling;
6903 				}
6904 
6905 				if (parent == root)
6906 					return clonedParent;
6907 
6908 				next = parent.previousSibling;
6909 				parent = parent.parentNode;
6910 
6911 				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
6912 
6913 				if (how != DELETE)
6914 					clonedGrandParent.appendChild(clonedParent);
6915 
6916 				clonedParent = clonedGrandParent;
6917 			}
6918 		};
6919 
6920 		function _traverseLeftBoundary(root, how) {
6921 			var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
6922 
6923 			if (next == root)
6924 				return _traverseNode(next, isFullySelected, TRUE, how);
6925 
6926 			parent = next.parentNode;
6927 			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
6928 
6929 			while (parent) {
6930 				while (next) {
6931 					nextSibling = next.nextSibling;
6932 					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
6933 
6934 					if (how != DELETE)
6935 						clonedParent.appendChild(clonedChild);
6936 
6937 					isFullySelected = TRUE;
6938 					next = nextSibling;
6939 				}
6940 
6941 				if (parent == root)
6942 					return clonedParent;
6943 
6944 				next = parent.nextSibling;
6945 				parent = parent.parentNode;
6946 
6947 				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
6948 
6949 				if (how != DELETE)
6950 					clonedGrandParent.appendChild(clonedParent);
6951 
6952 				clonedParent = clonedGrandParent;
6953 			}
6954 		};
6955 
6956 		function _traverseNode(n, isFullySelected, isLeft, how) {
6957 			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
6958 
6959 			if (isFullySelected)
6960 				return _traverseFullySelected(n, how);
6961 
6962 			if (n.nodeType == 3 /* TEXT_NODE */) {
6963 				txtValue = n.nodeValue;
6964 
6965 				if (isLeft) {
6966 					offset = t[START_OFFSET];
6967 					newNodeValue = txtValue.substring(offset);
6968 					oldNodeValue = txtValue.substring(0, offset);
6969 				} else {
6970 					offset = t[END_OFFSET];
6971 					newNodeValue = txtValue.substring(0, offset);
6972 					oldNodeValue = txtValue.substring(offset);
6973 				}
6974 
6975 				if (how != CLONE)
6976 					n.nodeValue = oldNodeValue;
6977 
6978 				if (how == DELETE)
6979 					return;
6980 
6981 				newNode = dom.clone(n, FALSE);
6982 				newNode.nodeValue = newNodeValue;
6983 
6984 				return newNode;
6985 			}
6986 
6987 			if (how == DELETE)
6988 				return;
6989 
6990 			return dom.clone(n, FALSE);
6991 		};
6992 
6993 		function _traverseFullySelected(n, how) {
6994 			if (how != DELETE)
6995 				return how == CLONE ? dom.clone(n, TRUE) : n;
6996 
6997 			n.parentNode.removeChild(n);
6998 		};
6999 
7000 		function toStringIE() {
7001 			return dom.create('body', null, cloneContents()).outerText;
7002 		}
7003 		
7004 		return t;
7005 	};
7006 
7007 	ns.Range = Range;
7008 
7009 	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
7010 	Range.prototype.toString = function() {
7011 		return this.toStringIE();
7012 	};
7013 })(tinymce.dom);
7014 
7015 (function() {
7016 	function Selection(selection) {
7017 		var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7018 
7019 		function getPosition(rng, start) {
7020 			var checkRng, startIndex = 0, endIndex, inside,
7021 				children, child, offset, index, position = -1, parent;
7022 
7023 			// Setup test range, collapse it and get the parent
7024 			checkRng = rng.duplicate();
7025 			checkRng.collapse(start);
7026 			parent = checkRng.parentElement();
7027 
7028 			// Check if the selection is within the right document
7029 			if (parent.ownerDocument !== selection.dom.doc)
7030 				return;
7031 
7032 			// IE will report non editable elements as it's parent so look for an editable one
7033 			while (parent.contentEditable === "false") {
7034 				parent = parent.parentNode;
7035 			}
7036 
7037 			// If parent doesn't have any children then return that we are inside the element
7038 			if (!parent.hasChildNodes()) {
7039 				return {node : parent, inside : 1};
7040 			}
7041 
7042 			// Setup node list and endIndex
7043 			children = parent.children;
7044 			endIndex = children.length - 1;
7045 
7046 			// Perform a binary search for the position
7047 			while (startIndex <= endIndex) {
7048 				index = Math.floor((startIndex + endIndex) / 2);
7049 
7050 				// Move selection to node and compare the ranges
7051 				child = children[index];
7052 				checkRng.moveToElementText(child);
7053 				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7054 
7055 				// Before/after or an exact match
7056 				if (position > 0) {
7057 					endIndex = index - 1;
7058 				} else if (position < 0) {
7059 					startIndex = index + 1;
7060 				} else {
7061 					return {node : child};
7062 				}
7063 			}
7064 
7065 			// Check if child position is before or we didn't find a position
7066 			if (position < 0) {
7067 				// No element child was found use the parent element and the offset inside that
7068 				if (!child) {
7069 					checkRng.moveToElementText(parent);
7070 					checkRng.collapse(true);
7071 					child = parent;
7072 					inside = true;
7073 				} else
7074 					checkRng.collapse(false);
7075 
7076 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7077 				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
7078 				offset = 0;
7079 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7080 					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7081 						break;
7082 					}
7083 
7084 					offset++;
7085 				}
7086 			} else {
7087 				// Child position is after the selection endpoint
7088 				checkRng.collapse(true);
7089 
7090 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7091 				offset = 0;
7092 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7093 					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7094 						break;
7095 					}
7096 
7097 					offset++;
7098 				}
7099 			}
7100 
7101 			return {node : child, position : position, offset : offset, inside : inside};
7102 		};
7103 
7104 		// Returns a W3C DOM compatible range object by using the IE Range API
7105 		function getRange() {
7106 			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7107 
7108 			// If selection is outside the current document just return an empty range
7109 			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7110 			if (element.ownerDocument != dom.doc)
7111 				return domRange;
7112 
7113 			collapsed = selection.isCollapsed();
7114 
7115 			// Handle control selection
7116 			if (ieRange.item) {
7117 				domRange.setStart(element.parentNode, dom.nodeIndex(element));
7118 				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7119 
7120 				return domRange;
7121 			}
7122 
7123 			function findEndPoint(start) {
7124 				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7125 
7126 				container = endPoint.node;
7127 				offset = endPoint.offset;
7128 
7129 				if (endPoint.inside && !container.hasChildNodes()) {
7130 					domRange[start ? 'setStart' : 'setEnd'](container, 0);
7131 					return;
7132 				}
7133 
7134 				if (offset === undef) {
7135 					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7136 					return;
7137 				}
7138 
7139 				if (endPoint.position < 0) {
7140 					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7141 
7142 					if (!sibling) {
7143 						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7144 						return;
7145 					}
7146 
7147 					if (!offset) {
7148 						if (sibling.nodeType == 3)
7149 							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7150 						else
7151 							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7152 
7153 						return;
7154 					}
7155 
7156 					// Find the text node and offset
7157 					while (sibling) {
7158 						nodeValue = sibling.nodeValue;
7159 						textNodeOffset += nodeValue.length;
7160 
7161 						// We are at or passed the position we where looking for
7162 						if (textNodeOffset >= offset) {
7163 							container = sibling;
7164 							textNodeOffset -= offset;
7165 							textNodeOffset = nodeValue.length - textNodeOffset;
7166 							break;
7167 						}
7168 
7169 						sibling = sibling.nextSibling;
7170 					}
7171 				} else {
7172 					// Find the text node and offset
7173 					sibling = container.previousSibling;
7174 
7175 					if (!sibling)
7176 						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7177 
7178 					// If there isn't any text to loop then use the first position
7179 					if (!offset) {
7180 						if (container.nodeType == 3)
7181 							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7182 						else
7183 							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7184 
7185 						return;
7186 					}
7187 
7188 					while (sibling) {
7189 						textNodeOffset += sibling.nodeValue.length;
7190 
7191 						// We are at or passed the position we where looking for
7192 						if (textNodeOffset >= offset) {
7193 							container = sibling;
7194 							textNodeOffset -= offset;
7195 							break;
7196 						}
7197 
7198 						sibling = sibling.previousSibling;
7199 					}
7200 				}
7201 
7202 				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7203 			};
7204 
7205 			try {
7206 				// Find start point
7207 				findEndPoint(true);
7208 
7209 				// Find end point if needed
7210 				if (!collapsed)
7211 					findEndPoint();
7212 			} catch (ex) {
7213 				// IE has a nasty bug where text nodes might throw "invalid argument" when you
7214 				// access the nodeValue or other properties of text nodes. This seems to happend when
7215 				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7216 				if (ex.number == -2147024809) {
7217 					// Get the current selection
7218 					bookmark = self.getBookmark(2);
7219 
7220 					// Get start element
7221 					tmpRange = ieRange.duplicate();
7222 					tmpRange.collapse(true);
7223 					element = tmpRange.parentElement();
7224 
7225 					// Get end element
7226 					if (!collapsed) {
7227 						tmpRange = ieRange.duplicate();
7228 						tmpRange.collapse(false);
7229 						element2 = tmpRange.parentElement();
7230 						element2.innerHTML = element2.innerHTML;
7231 					}
7232 
7233 					// Remove the broken elements
7234 					element.innerHTML = element.innerHTML;
7235 
7236 					// Restore the selection
7237 					self.moveToBookmark(bookmark);
7238 
7239 					// Since the range has moved we need to re-get it
7240 					ieRange = selection.getRng();
7241 
7242 					// Find start point
7243 					findEndPoint(true);
7244 
7245 					// Find end point if needed
7246 					if (!collapsed)
7247 						findEndPoint();
7248 				} else
7249 					throw ex; // Throw other errors
7250 			}
7251 
7252 			return domRange;
7253 		};
7254 
7255 		this.getBookmark = function(type) {
7256 			var rng = selection.getRng(), start, end, bookmark = {};
7257 
7258 			function getIndexes(node) {
7259 				var parent, root, children, i, indexes = [];
7260 
7261 				parent = node.parentNode;
7262 				root = dom.getRoot().parentNode;
7263 
7264 				while (parent != root && parent.nodeType !== 9) {
7265 					children = parent.children;
7266 
7267 					i = children.length;
7268 					while (i--) {
7269 						if (node === children[i]) {
7270 							indexes.push(i);
7271 							break;
7272 						}
7273 					}
7274 
7275 					node = parent;
7276 					parent = parent.parentNode;
7277 				}
7278 
7279 				return indexes;
7280 			};
7281 
7282 			function getBookmarkEndPoint(start) {
7283 				var position;
7284 
7285 				position = getPosition(rng, start);
7286 				if (position) {
7287 					return {
7288 						position : position.position,
7289 						offset : position.offset,
7290 						indexes : getIndexes(position.node),
7291 						inside : position.inside
7292 					};
7293 				}
7294 			};
7295 
7296 			// Non ubstructive bookmark
7297 			if (type === 2) {
7298 				// Handle text selection
7299 				if (!rng.item) {
7300 					bookmark.start = getBookmarkEndPoint(true);
7301 
7302 					if (!selection.isCollapsed())
7303 						bookmark.end = getBookmarkEndPoint();
7304 				} else
7305 					bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7306 			}
7307 
7308 			return bookmark;
7309 		};
7310 
7311 		this.moveToBookmark = function(bookmark) {
7312 			var rng, body = dom.doc.body;
7313 
7314 			function resolveIndexes(indexes) {
7315 				var node, i, idx, children;
7316 
7317 				node = dom.getRoot();
7318 				for (i = indexes.length - 1; i >= 0; i--) {
7319 					children = node.children;
7320 					idx = indexes[i];
7321 
7322 					if (idx <= children.length - 1) {
7323 						node = children[idx];
7324 					}
7325 				}
7326 
7327 				return node;
7328 			};
7329 			
7330 			function setBookmarkEndPoint(start) {
7331 				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7332 
7333 				if (endPoint) {
7334 					moveLeft = endPoint.position > 0;
7335 
7336 					moveRng = body.createTextRange();
7337 					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7338 
7339 					offset = endPoint.offset;
7340 					if (offset !== undef) {
7341 						moveRng.collapse(endPoint.inside || moveLeft);
7342 						moveRng.moveStart('character', moveLeft ? -offset : offset);
7343 					} else
7344 						moveRng.collapse(start);
7345 
7346 					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7347 
7348 					if (start)
7349 						rng.collapse(true);
7350 				}
7351 			};
7352 
7353 			if (bookmark.start) {
7354 				if (bookmark.start.ctrl) {
7355 					rng = body.createControlRange();
7356 					rng.addElement(resolveIndexes(bookmark.start.indexes));
7357 					rng.select();
7358 				} else {
7359 					rng = body.createTextRange();
7360 					setBookmarkEndPoint(true);
7361 					setBookmarkEndPoint();
7362 					rng.select();
7363 				}
7364 			}
7365 		};
7366 
7367 		this.addRange = function(rng) {
7368 			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body;
7369 
7370 			function setEndPoint(start) {
7371 				var container, offset, marker, tmpRng, nodes;
7372 
7373 				marker = dom.create('a');
7374 				container = start ? startContainer : endContainer;
7375 				offset = start ? startOffset : endOffset;
7376 				tmpRng = ieRng.duplicate();
7377 
7378 				if (container == doc || container == doc.documentElement) {
7379 					container = body;
7380 					offset = 0;
7381 				}
7382 
7383 				if (container.nodeType == 3) {
7384 					container.parentNode.insertBefore(marker, container);
7385 					tmpRng.moveToElementText(marker);
7386 					tmpRng.moveStart('character', offset);
7387 					dom.remove(marker);
7388 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7389 				} else {
7390 					nodes = container.childNodes;
7391 
7392 					if (nodes.length) {
7393 						if (offset >= nodes.length) {
7394 							dom.insertAfter(marker, nodes[nodes.length - 1]);
7395 						} else {
7396 							container.insertBefore(marker, nodes[offset]);
7397 						}
7398 
7399 						tmpRng.moveToElementText(marker);
7400 					} else if (container.canHaveHTML) {
7401 						// Empty node selection for example <div>|</div>
7402 						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7403 						container.innerHTML = '<span>\uFEFF</span>';
7404 						marker = container.firstChild;
7405 						tmpRng.moveToElementText(marker);
7406 						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7407 					}
7408 
7409 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7410 					dom.remove(marker);
7411 				}
7412 			}
7413 
7414 			// Setup some shorter versions
7415 			startContainer = rng.startContainer;
7416 			startOffset = rng.startOffset;
7417 			endContainer = rng.endContainer;
7418 			endOffset = rng.endOffset;
7419 			ieRng = body.createTextRange();
7420 
7421 			// If single element selection then try making a control selection out of it
7422 			if (startContainer == endContainer && startContainer.nodeType == 1) {
7423 				// Trick to place the caret inside an empty block element like <p></p>
7424 				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7425 					if (startContainer.canHaveHTML) {
7426 						// Check if previous sibling is an empty block if it is then we need to render it
7427 						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7428 						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7429 						sibling = startContainer.previousSibling;
7430 						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7431 							sibling.innerHTML = '\uFEFF';
7432 						} else {
7433 							sibling = null;
7434 						}
7435 
7436 						startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7437 						ieRng.moveToElementText(startContainer.lastChild);
7438 						ieRng.select();
7439 						dom.doc.selection.clear();
7440 						startContainer.innerHTML = '';
7441 
7442 						if (sibling) {
7443 							sibling.innerHTML = '';
7444 						}
7445 						return;
7446 					} else {
7447 						startOffset = dom.nodeIndex(startContainer);
7448 						startContainer = startContainer.parentNode;
7449 					}
7450 				}
7451 
7452 				if (startOffset == endOffset - 1) {
7453 					try {
7454 						ctrlRng = body.createControlRange();
7455 						ctrlRng.addElement(startContainer.childNodes[startOffset]);
7456 						ctrlRng.select();
7457 						return;
7458 					} catch (ex) {
7459 						// Ignore
7460 					}
7461 				}
7462 			}
7463 
7464 			// Set start/end point of selection
7465 			setEndPoint(true);
7466 			setEndPoint();
7467 
7468 			// Select the new range and scroll it into view
7469 			ieRng.select();
7470 		};
7471 
7472 		// Expose range method
7473 		this.getRangeAt = getRange;
7474 	};
7475 
7476 	// Expose the selection object
7477 	tinymce.dom.TridentSelection = Selection;
7478 })();
7479 
7480 
7481 /*
7482  * Sizzle CSS Selector Engine
7483  *  Copyright, The Dojo Foundation
7484  *  Released under the MIT, BSD, and GPL Licenses.
7485  *  More information: http://sizzlejs.com/
7486  */
7487 (function(){
7488 
7489 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
7490 	expando = "sizcache",
7491 	done = 0,
7492 	toString = Object.prototype.toString,
7493 	hasDuplicate = false,
7494 	baseHasDuplicate = true,
7495 	rBackslash = /\\/g,
7496 	rReturn = /\r\n/g,
7497 	rNonWord = /\W/;
7498 
7499 // Here we check if the JavaScript engine is using some sort of
7500 // optimization where it does not always call our comparision
7501 // function. If that is the case, discard the hasDuplicate value.
7502 //   Thus far that includes Google Chrome.
7503 [0, 0].sort(function() {
7504 	baseHasDuplicate = false;
7505 	return 0;
7506 });
7507 
7508 var Sizzle = function( selector, context, results, seed ) {
7509 	results = results || [];
7510 	context = context || document;
7511 
7512 	var origContext = context;
7513 
7514 	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
7515 		return [];
7516 	}
7517 
7518 	if ( !selector || typeof selector !== "string" ) {
7519 		return results;
7520 	}
7521 
7522 	var m, set, checkSet, extra, ret, cur, pop, i,
7523 		prune = true,
7524 		contextXML = Sizzle.isXML( context ),
7525 		parts = [],
7526 		soFar = selector;
7527 
7528 	// Reset the position of the chunker regexp (start from head)
7529 	do {
7530 		chunker.exec( "" );
7531 		m = chunker.exec( soFar );
7532 
7533 		if ( m ) {
7534 			soFar = m[3];
7535 
7536 			parts.push( m[1] );
7537 
7538 			if ( m[2] ) {
7539 				extra = m[3];
7540 				break;
7541 			}
7542 		}
7543 	} while ( m );
7544 
7545 	if ( parts.length > 1 && origPOS.exec( selector ) ) {
7546 
7547 		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
7548 			set = posProcess( parts[0] + parts[1], context, seed );
7549 
7550 		} else {
7551 			set = Expr.relative[ parts[0] ] ?
7552 				[ context ] :
7553 				Sizzle( parts.shift(), context );
7554 
7555 			while ( parts.length ) {
7556 				selector = parts.shift();
7557 
7558 				if ( Expr.relative[ selector ] ) {
7559 					selector += parts.shift();
7560 				}
7561 
7562 				set = posProcess( selector, set, seed );
7563 			}
7564 		}
7565 
7566 	} else {
7567 		// Take a shortcut and set the context if the root selector is an ID
7568 		// (but not if it'll be faster if the inner selector is an ID)
7569 		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
7570 				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
7571 
7572 			ret = Sizzle.find( parts.shift(), context, contextXML );
7573 			context = ret.expr ?
7574 				Sizzle.filter( ret.expr, ret.set )[0] :
7575 				ret.set[0];
7576 		}
7577 
7578 		if ( context ) {
7579 			ret = seed ?
7580 				{ expr: parts.pop(), set: makeArray(seed) } :
7581 				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
7582 
7583 			set = ret.expr ?
7584 				Sizzle.filter( ret.expr, ret.set ) :
7585 				ret.set;
7586 
7587 			if ( parts.length > 0 ) {
7588 				checkSet = makeArray( set );
7589 
7590 			} else {
7591 				prune = false;
7592 			}
7593 
7594 			while ( parts.length ) {
7595 				cur = parts.pop();
7596 				pop = cur;
7597 
7598 				if ( !Expr.relative[ cur ] ) {
7599 					cur = "";
7600 				} else {
7601 					pop = parts.pop();
7602 				}
7603 
7604 				if ( pop == null ) {
7605 					pop = context;
7606 				}
7607 
7608 				Expr.relative[ cur ]( checkSet, pop, contextXML );
7609 			}
7610 
7611 		} else {
7612 			checkSet = parts = [];
7613 		}
7614 	}
7615 
7616 	if ( !checkSet ) {
7617 		checkSet = set;
7618 	}
7619 
7620 	if ( !checkSet ) {
7621 		Sizzle.error( cur || selector );
7622 	}
7623 
7624 	if ( toString.call(checkSet) === "[object Array]" ) {
7625 		if ( !prune ) {
7626 			results.push.apply( results, checkSet );
7627 
7628 		} else if ( context && context.nodeType === 1 ) {
7629 			for ( i = 0; checkSet[i] != null; i++ ) {
7630 				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
7631 					results.push( set[i] );
7632 				}
7633 			}
7634 
7635 		} else {
7636 			for ( i = 0; checkSet[i] != null; i++ ) {
7637 				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
7638 					results.push( set[i] );
7639 				}
7640 			}
7641 		}
7642 
7643 	} else {
7644 		makeArray( checkSet, results );
7645 	}
7646 
7647 	if ( extra ) {
7648 		Sizzle( extra, origContext, results, seed );
7649 		Sizzle.uniqueSort( results );
7650 	}
7651 
7652 	return results;
7653 };
7654 
7655 Sizzle.uniqueSort = function( results ) {
7656 	if ( sortOrder ) {
7657 		hasDuplicate = baseHasDuplicate;
7658 		results.sort( sortOrder );
7659 
7660 		if ( hasDuplicate ) {
7661 			for ( var i = 1; i < results.length; i++ ) {
7662 				if ( results[i] === results[ i - 1 ] ) {
7663 					results.splice( i--, 1 );
7664 				}
7665 			}
7666 		}
7667 	}
7668 
7669 	return results;
7670 };
7671 
7672 Sizzle.matches = function( expr, set ) {
7673 	return Sizzle( expr, null, null, set );
7674 };
7675 
7676 Sizzle.matchesSelector = function( node, expr ) {
7677 	return Sizzle( expr, null, null, [node] ).length > 0;
7678 };
7679 
7680 Sizzle.find = function( expr, context, isXML ) {
7681 	var set, i, len, match, type, left;
7682 
7683 	if ( !expr ) {
7684 		return [];
7685 	}
7686 
7687 	for ( i = 0, len = Expr.order.length; i < len; i++ ) {
7688 		type = Expr.order[i];
7689 
7690 		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
7691 			left = match[1];
7692 			match.splice( 1, 1 );
7693 
7694 			if ( left.substr( left.length - 1 ) !== "\\" ) {
7695 				match[1] = (match[1] || "").replace( rBackslash, "" );
7696 				set = Expr.find[ type ]( match, context, isXML );
7697 
7698 				if ( set != null ) {
7699 					expr = expr.replace( Expr.match[ type ], "" );
7700 					break;
7701 				}
7702 			}
7703 		}
7704 	}
7705 
7706 	if ( !set ) {
7707 		set = typeof context.getElementsByTagName !== "undefined" ?
7708 			context.getElementsByTagName( "*" ) :
7709 			[];
7710 	}
7711 
7712 	return { set: set, expr: expr };
7713 };
7714 
7715 Sizzle.filter = function( expr, set, inplace, not ) {
7716 	var match, anyFound,
7717 		type, found, item, filter, left,
7718 		i, pass,
7719 		old = expr,
7720 		result = [],
7721 		curLoop = set,
7722 		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
7723 
7724 	while ( expr && set.length ) {
7725 		for ( type in Expr.filter ) {
7726 			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
7727 				filter = Expr.filter[ type ];
7728 				left = match[1];
7729 
7730 				anyFound = false;
7731 
7732 				match.splice(1,1);
7733 
7734 				if ( left.substr( left.length - 1 ) === "\\" ) {
7735 					continue;
7736 				}
7737 
7738 				if ( curLoop === result ) {
7739 					result = [];
7740 				}
7741 
7742 				if ( Expr.preFilter[ type ] ) {
7743 					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
7744 
7745 					if ( !match ) {
7746 						anyFound = found = true;
7747 
7748 					} else if ( match === true ) {
7749 						continue;
7750 					}
7751 				}
7752 
7753 				if ( match ) {
7754 					for ( i = 0; (item = curLoop[i]) != null; i++ ) {
7755 						if ( item ) {
7756 							found = filter( item, match, i, curLoop );
7757 							pass = not ^ found;
7758 
7759 							if ( inplace && found != null ) {
7760 								if ( pass ) {
7761 									anyFound = true;
7762 
7763 								} else {
7764 									curLoop[i] = false;
7765 								}
7766 
7767 							} else if ( pass ) {
7768 								result.push( item );
7769 								anyFound = true;
7770 							}
7771 						}
7772 					}
7773 				}
7774 
7775 				if ( found !== undefined ) {
7776 					if ( !inplace ) {
7777 						curLoop = result;
7778 					}
7779 
7780 					expr = expr.replace( Expr.match[ type ], "" );
7781 
7782 					if ( !anyFound ) {
7783 						return [];
7784 					}
7785 
7786 					break;
7787 				}
7788 			}
7789 		}
7790 
7791 		// Improper expression
7792 		if ( expr === old ) {
7793 			if ( anyFound == null ) {
7794 				Sizzle.error( expr );
7795 
7796 			} else {
7797 				break;
7798 			}
7799 		}
7800 
7801 		old = expr;
7802 	}
7803 
7804 	return curLoop;
7805 };
7806 
7807 Sizzle.error = function( msg ) {
7808 	throw new Error( "Syntax error, unrecognized expression: " + msg );
7809 };
7810 
7811 var getText = Sizzle.getText = function( elem ) {
7812     var i, node,
7813 		nodeType = elem.nodeType,
7814 		ret = "";
7815 
7816 	if ( nodeType ) {
7817 		if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
7818 			// Use textContent || innerText for elements
7819 			if ( typeof elem.textContent === 'string' ) {
7820 				return elem.textContent;
7821 			} else if ( typeof elem.innerText === 'string' ) {
7822 				// Replace IE's carriage returns
7823 				return elem.innerText.replace( rReturn, '' );
7824 			} else {
7825 				// Traverse it's children
7826 				for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
7827 					ret += getText( elem );
7828 				}
7829 			}
7830 		} else if ( nodeType === 3 || nodeType === 4 ) {
7831 			return elem.nodeValue;
7832 		}
7833 	} else {
7834 
7835 		// If no nodeType, this is expected to be an array
7836 		for ( i = 0; (node = elem[i]); i++ ) {
7837 			// Do not traverse comment nodes
7838 			if ( node.nodeType !== 8 ) {
7839 				ret += getText( node );
7840 			}
7841 		}
7842 	}
7843 	return ret;
7844 };
7845 
7846 var Expr = Sizzle.selectors = {
7847 	order: [ "ID", "NAME", "TAG" ],
7848 
7849 	match: {
7850 		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7851 		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7852 		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
7853 		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
7854 		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
7855 		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
7856 		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
7857 		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
7858 	},
7859 
7860 	leftMatch: {},
7861 
7862 	attrMap: {
7863 		"class": "className",
7864 		"for": "htmlFor"
7865 	},
7866 
7867 	attrHandle: {
7868 		href: function( elem ) {
7869 			return elem.getAttribute( "href" );
7870 		},
7871 		type: function( elem ) {
7872 			return elem.getAttribute( "type" );
7873 		}
7874 	},
7875 
7876 	relative: {
7877 		"+": function(checkSet, part){
7878 			var isPartStr = typeof part === "string",
7879 				isTag = isPartStr && !rNonWord.test( part ),
7880 				isPartStrNotTag = isPartStr && !isTag;
7881 
7882 			if ( isTag ) {
7883 				part = part.toLowerCase();
7884 			}
7885 
7886 			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
7887 				if ( (elem = checkSet[i]) ) {
7888 					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
7889 
7890 					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
7891 						elem || false :
7892 						elem === part;
7893 				}
7894 			}
7895 
7896 			if ( isPartStrNotTag ) {
7897 				Sizzle.filter( part, checkSet, true );
7898 			}
7899 		},
7900 
7901 		">": function( checkSet, part ) {
7902 			var elem,
7903 				isPartStr = typeof part === "string",
7904 				i = 0,
7905 				l = checkSet.length;
7906 
7907 			if ( isPartStr && !rNonWord.test( part ) ) {
7908 				part = part.toLowerCase();
7909 
7910 				for ( ; i < l; i++ ) {
7911 					elem = checkSet[i];
7912 
7913 					if ( elem ) {
7914 						var parent = elem.parentNode;
7915 						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
7916 					}
7917 				}
7918 
7919 			} else {
7920 				for ( ; i < l; i++ ) {
7921 					elem = checkSet[i];
7922 
7923 					if ( elem ) {
7924 						checkSet[i] = isPartStr ?
7925 							elem.parentNode :
7926 							elem.parentNode === part;
7927 					}
7928 				}
7929 
7930 				if ( isPartStr ) {
7931 					Sizzle.filter( part, checkSet, true );
7932 				}
7933 			}
7934 		},
7935 
7936 		"": function(checkSet, part, isXML){
7937 			var nodeCheck,
7938 				doneName = done++,
7939 				checkFn = dirCheck;
7940 
7941 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7942 				part = part.toLowerCase();
7943 				nodeCheck = part;
7944 				checkFn = dirNodeCheck;
7945 			}
7946 
7947 			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
7948 		},
7949 
7950 		"~": function( checkSet, part, isXML ) {
7951 			var nodeCheck,
7952 				doneName = done++,
7953 				checkFn = dirCheck;
7954 
7955 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7956 				part = part.toLowerCase();
7957 				nodeCheck = part;
7958 				checkFn = dirNodeCheck;
7959 			}
7960 
7961 			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
7962 		}
7963 	},
7964 
7965 	find: {
7966 		ID: function( match, context, isXML ) {
7967 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
7968 				var m = context.getElementById(match[1]);
7969 				// Check parentNode to catch when Blackberry 4.6 returns
7970 				// nodes that are no longer in the document #6963
7971 				return m && m.parentNode ? [m] : [];
7972 			}
7973 		},
7974 
7975 		NAME: function( match, context ) {
7976 			if ( typeof context.getElementsByName !== "undefined" ) {
7977 				var ret = [],
7978 					results = context.getElementsByName( match[1] );
7979 
7980 				for ( var i = 0, l = results.length; i < l; i++ ) {
7981 					if ( results[i].getAttribute("name") === match[1] ) {
7982 						ret.push( results[i] );
7983 					}
7984 				}
7985 
7986 				return ret.length === 0 ? null : ret;
7987 			}
7988 		},
7989 
7990 		TAG: function( match, context ) {
7991 			if ( typeof context.getElementsByTagName !== "undefined" ) {
7992 				return context.getElementsByTagName( match[1] );
7993 			}
7994 		}
7995 	},
7996 	preFilter: {
7997 		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
7998 			match = " " + match[1].replace( rBackslash, "" ) + " ";
7999 
8000 			if ( isXML ) {
8001 				return match;
8002 			}
8003 
8004 			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
8005 				if ( elem ) {
8006 					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
8007 						if ( !inplace ) {
8008 							result.push( elem );
8009 						}
8010 
8011 					} else if ( inplace ) {
8012 						curLoop[i] = false;
8013 					}
8014 				}
8015 			}
8016 
8017 			return false;
8018 		},
8019 
8020 		ID: function( match ) {
8021 			return match[1].replace( rBackslash, "" );
8022 		},
8023 
8024 		TAG: function( match, curLoop ) {
8025 			return match[1].replace( rBackslash, "" ).toLowerCase();
8026 		},
8027 
8028 		CHILD: function( match ) {
8029 			if ( match[1] === "nth" ) {
8030 				if ( !match[2] ) {
8031 					Sizzle.error( match[0] );
8032 				}
8033 
8034 				match[2] = match[2].replace(/^\+|\s*/g, '');
8035 
8036 				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
8037 				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
8038 					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
8039 					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
8040 
8041 				// calculate the numbers (first)n+(last) including if they are negative
8042 				match[2] = (test[1] + (test[2] || 1)) - 0;
8043 				match[3] = test[3] - 0;
8044 			}
8045 			else if ( match[2] ) {
8046 				Sizzle.error( match[0] );
8047 			}
8048 
8049 			// TODO: Move to normal caching system
8050 			match[0] = done++;
8051 
8052 			return match;
8053 		},
8054 
8055 		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
8056 			var name = match[1] = match[1].replace( rBackslash, "" );
8057 
8058 			if ( !isXML && Expr.attrMap[name] ) {
8059 				match[1] = Expr.attrMap[name];
8060 			}
8061 
8062 			// Handle if an un-quoted value was used
8063 			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
8064 
8065 			if ( match[2] === "~=" ) {
8066 				match[4] = " " + match[4] + " ";
8067 			}
8068 
8069 			return match;
8070 		},
8071 
8072 		PSEUDO: function( match, curLoop, inplace, result, not ) {
8073 			if ( match[1] === "not" ) {
8074 				// If we're dealing with a complex expression, or a simple one
8075 				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
8076 					match[3] = Sizzle(match[3], null, null, curLoop);
8077 
8078 				} else {
8079 					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
8080 
8081 					if ( !inplace ) {
8082 						result.push.apply( result, ret );
8083 					}
8084 
8085 					return false;
8086 				}
8087 
8088 			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
8089 				return true;
8090 			}
8091 
8092 			return match;
8093 		},
8094 
8095 		POS: function( match ) {
8096 			match.unshift( true );
8097 
8098 			return match;
8099 		}
8100 	},
8101 
8102 	filters: {
8103 		enabled: function( elem ) {
8104 			return elem.disabled === false && elem.type !== "hidden";
8105 		},
8106 
8107 		disabled: function( elem ) {
8108 			return elem.disabled === true;
8109 		},
8110 
8111 		checked: function( elem ) {
8112 			return elem.checked === true;
8113 		},
8114 
8115 		selected: function( elem ) {
8116 			// Accessing this property makes selected-by-default
8117 			// options in Safari work properly
8118 			if ( elem.parentNode ) {
8119 				elem.parentNode.selectedIndex;
8120 			}
8121 
8122 			return elem.selected === true;
8123 		},
8124 
8125 		parent: function( elem ) {
8126 			return !!elem.firstChild;
8127 		},
8128 
8129 		empty: function( elem ) {
8130 			return !elem.firstChild;
8131 		},
8132 
8133 		has: function( elem, i, match ) {
8134 			return !!Sizzle( match[3], elem ).length;
8135 		},
8136 
8137 		header: function( elem ) {
8138 			return (/h\d/i).test( elem.nodeName );
8139 		},
8140 
8141 		text: function( elem ) {
8142 			var attr = elem.getAttribute( "type" ), type = elem.type;
8143 			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
8144 			// use getAttribute instead to test this case
8145 			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
8146 		},
8147 
8148 		radio: function( elem ) {
8149 			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
8150 		},
8151 
8152 		checkbox: function( elem ) {
8153 			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
8154 		},
8155 
8156 		file: function( elem ) {
8157 			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
8158 		},
8159 
8160 		password: function( elem ) {
8161 			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
8162 		},
8163 
8164 		submit: function( elem ) {
8165 			var name = elem.nodeName.toLowerCase();
8166 			return (name === "input" || name === "button") && "submit" === elem.type;
8167 		},
8168 
8169 		image: function( elem ) {
8170 			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
8171 		},
8172 
8173 		reset: function( elem ) {
8174 			var name = elem.nodeName.toLowerCase();
8175 			return (name === "input" || name === "button") && "reset" === elem.type;
8176 		},
8177 
8178 		button: function( elem ) {
8179 			var name = elem.nodeName.toLowerCase();
8180 			return name === "input" && "button" === elem.type || name === "button";
8181 		},
8182 
8183 		input: function( elem ) {
8184 			return (/input|select|textarea|button/i).test( elem.nodeName );
8185 		},
8186 
8187 		focus: function( elem ) {
8188 			return elem === elem.ownerDocument.activeElement;
8189 		}
8190 	},
8191 	setFilters: {
8192 		first: function( elem, i ) {
8193 			return i === 0;
8194 		},
8195 
8196 		last: function( elem, i, match, array ) {
8197 			return i === array.length - 1;
8198 		},
8199 
8200 		even: function( elem, i ) {
8201 			return i % 2 === 0;
8202 		},
8203 
8204 		odd: function( elem, i ) {
8205 			return i % 2 === 1;
8206 		},
8207 
8208 		lt: function( elem, i, match ) {
8209 			return i < match[3] - 0;
8210 		},
8211 
8212 		gt: function( elem, i, match ) {
8213 			return i > match[3] - 0;
8214 		},
8215 
8216 		nth: function( elem, i, match ) {
8217 			return match[3] - 0 === i;
8218 		},
8219 
8220 		eq: function( elem, i, match ) {
8221 			return match[3] - 0 === i;
8222 		}
8223 	},
8224 	filter: {
8225 		PSEUDO: function( elem, match, i, array ) {
8226 			var name = match[1],
8227 				filter = Expr.filters[ name ];
8228 
8229 			if ( filter ) {
8230 				return filter( elem, i, match, array );
8231 
8232 			} else if ( name === "contains" ) {
8233 				return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
8234 
8235 			} else if ( name === "not" ) {
8236 				var not = match[3];
8237 
8238 				for ( var j = 0, l = not.length; j < l; j++ ) {
8239 					if ( not[j] === elem ) {
8240 						return false;
8241 					}
8242 				}
8243 
8244 				return true;
8245 
8246 			} else {
8247 				Sizzle.error( name );
8248 			}
8249 		},
8250 
8251 		CHILD: function( elem, match ) {
8252 			var first, last,
8253 				doneName, parent, cache,
8254 				count, diff,
8255 				type = match[1],
8256 				node = elem;
8257 
8258 			switch ( type ) {
8259 				case "only":
8260 				case "first":
8261 					while ( (node = node.previousSibling) ) {
8262 						if ( node.nodeType === 1 ) {
8263 							return false;
8264 						}
8265 					}
8266 
8267 					if ( type === "first" ) {
8268 						return true;
8269 					}
8270 
8271 					node = elem;
8272 
8273 					/* falls through */
8274 				case "last":
8275 					while ( (node = node.nextSibling) ) {
8276 						if ( node.nodeType === 1 ) {
8277 							return false;
8278 						}
8279 					}
8280 
8281 					return true;
8282 
8283 				case "nth":
8284 					first = match[2];
8285 					last = match[3];
8286 
8287 					if ( first === 1 && last === 0 ) {
8288 						return true;
8289 					}
8290 
8291 					doneName = match[0];
8292 					parent = elem.parentNode;
8293 
8294 					if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
8295 						count = 0;
8296 
8297 						for ( node = parent.firstChild; node; node = node.nextSibling ) {
8298 							if ( node.nodeType === 1 ) {
8299 								node.nodeIndex = ++count;
8300 							}
8301 						}
8302 
8303 						parent[ expando ] = doneName;
8304 					}
8305 
8306 					diff = elem.nodeIndex - last;
8307 
8308 					if ( first === 0 ) {
8309 						return diff === 0;
8310 
8311 					} else {
8312 						return ( diff % first === 0 && diff / first >= 0 );
8313 					}
8314 			}
8315 		},
8316 
8317 		ID: function( elem, match ) {
8318 			return elem.nodeType === 1 && elem.getAttribute("id") === match;
8319 		},
8320 
8321 		TAG: function( elem, match ) {
8322 			return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
8323 		},
8324 
8325 		CLASS: function( elem, match ) {
8326 			return (" " + (elem.className || elem.getAttribute("class")) + " ")
8327 				.indexOf( match ) > -1;
8328 		},
8329 
8330 		ATTR: function( elem, match ) {
8331 			var name = match[1],
8332 				result = Sizzle.attr ?
8333 					Sizzle.attr( elem, name ) :
8334 					Expr.attrHandle[ name ] ?
8335 					Expr.attrHandle[ name ]( elem ) :
8336 					elem[ name ] != null ?
8337 						elem[ name ] :
8338 						elem.getAttribute( name ),
8339 				value = result + "",
8340 				type = match[2],
8341 				check = match[4];
8342 
8343 			return result == null ?
8344 				type === "!=" :
8345 				!type && Sizzle.attr ?
8346 				result != null :
8347 				type === "=" ?
8348 				value === check :
8349 				type === "*=" ?
8350 				value.indexOf(check) >= 0 :
8351 				type === "~=" ?
8352 				(" " + value + " ").indexOf(check) >= 0 :
8353 				!check ?
8354 				value && result !== false :
8355 				type === "!=" ?
8356 				value !== check :
8357 				type === "^=" ?
8358 				value.indexOf(check) === 0 :
8359 				type === "$=" ?
8360 				value.substr(value.length - check.length) === check :
8361 				type === "|=" ?
8362 				value === check || value.substr(0, check.length + 1) === check + "-" :
8363 				false;
8364 		},
8365 
8366 		POS: function( elem, match, i, array ) {
8367 			var name = match[2],
8368 				filter = Expr.setFilters[ name ];
8369 
8370 			if ( filter ) {
8371 				return filter( elem, i, match, array );
8372 			}
8373 		}
8374 	}
8375 };
8376 
8377 var origPOS = Expr.match.POS,
8378 	fescape = function(all, num){
8379 		return "\\" + (num - 0 + 1);
8380 	};
8381 
8382 for ( var type in Expr.match ) {
8383 	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
8384 	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
8385 }
8386 // Expose origPOS
8387 // "global" as in regardless of relation to brackets/parens
8388 Expr.match.globalPOS = origPOS;
8389 
8390 var makeArray = function( array, results ) {
8391 	array = Array.prototype.slice.call( array, 0 );
8392 
8393 	if ( results ) {
8394 		results.push.apply( results, array );
8395 		return results;
8396 	}
8397 
8398 	return array;
8399 };
8400 
8401 // Perform a simple check to determine if the browser is capable of
8402 // converting a NodeList to an array using builtin methods.
8403 // Also verifies that the returned array holds DOM nodes
8404 // (which is not the case in the Blackberry browser)
8405 try {
8406 	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
8407 
8408 // Provide a fallback method if it does not work
8409 } catch( e ) {
8410 	makeArray = function( array, results ) {
8411 		var i = 0,
8412 			ret = results || [];
8413 
8414 		if ( toString.call(array) === "[object Array]" ) {
8415 			Array.prototype.push.apply( ret, array );
8416 
8417 		} else {
8418 			if ( typeof array.length === "number" ) {
8419 				for ( var l = array.length; i < l; i++ ) {
8420 					ret.push( array[i] );
8421 				}
8422 
8423 			} else {
8424 				for ( ; array[i]; i++ ) {
8425 					ret.push( array[i] );
8426 				}
8427 			}
8428 		}
8429 
8430 		return ret;
8431 	};
8432 }
8433 
8434 var sortOrder, siblingCheck;
8435 
8436 if ( document.documentElement.compareDocumentPosition ) {
8437 	sortOrder = function( a, b ) {
8438 		if ( a === b ) {
8439 			hasDuplicate = true;
8440 			return 0;
8441 		}
8442 
8443 		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
8444 			return a.compareDocumentPosition ? -1 : 1;
8445 		}
8446 
8447 		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
8448 	};
8449 
8450 } else {
8451 	sortOrder = function( a, b ) {
8452 		// The nodes are identical, we can exit early
8453 		if ( a === b ) {
8454 			hasDuplicate = true;
8455 			return 0;
8456 
8457 		// Fallback to using sourceIndex (in IE) if it's available on both nodes
8458 		} else if ( a.sourceIndex && b.sourceIndex ) {
8459 			return a.sourceIndex - b.sourceIndex;
8460 		}
8461 
8462 		var al, bl,
8463 			ap = [],
8464 			bp = [],
8465 			aup = a.parentNode,
8466 			bup = b.parentNode,
8467 			cur = aup;
8468 
8469 		// If the nodes are siblings (or identical) we can do a quick check
8470 		if ( aup === bup ) {
8471 			return siblingCheck( a, b );
8472 
8473 		// If no parents were found then the nodes are disconnected
8474 		} else if ( !aup ) {
8475 			return -1;
8476 
8477 		} else if ( !bup ) {
8478 			return 1;
8479 		}
8480 
8481 		// Otherwise they're somewhere else in the tree so we need
8482 		// to build up a full list of the parentNodes for comparison
8483 		while ( cur ) {
8484 			ap.unshift( cur );
8485 			cur = cur.parentNode;
8486 		}
8487 
8488 		cur = bup;
8489 
8490 		while ( cur ) {
8491 			bp.unshift( cur );
8492 			cur = cur.parentNode;
8493 		}
8494 
8495 		al = ap.length;
8496 		bl = bp.length;
8497 
8498 		// Start walking down the tree looking for a discrepancy
8499 		for ( var i = 0; i < al && i < bl; i++ ) {
8500 			if ( ap[i] !== bp[i] ) {
8501 				return siblingCheck( ap[i], bp[i] );
8502 			}
8503 		}
8504 
8505 		// We ended someplace up the tree so do a sibling check
8506 		return i === al ?
8507 			siblingCheck( a, bp[i], -1 ) :
8508 			siblingCheck( ap[i], b, 1 );
8509 	};
8510 
8511 	siblingCheck = function( a, b, ret ) {
8512 		if ( a === b ) {
8513 			return ret;
8514 		}
8515 
8516 		var cur = a.nextSibling;
8517 
8518 		while ( cur ) {
8519 			if ( cur === b ) {
8520 				return -1;
8521 			}
8522 
8523 			cur = cur.nextSibling;
8524 		}
8525 
8526 		return 1;
8527 	};
8528 }
8529 
8530 // Check to see if the browser returns elements by name when
8531 // querying by getElementById (and provide a workaround)
8532 (function(){
8533 	// We're going to inject a fake input element with a specified name
8534 	var form = document.createElement("div"),
8535 		id = "script" + (new Date()).getTime(),
8536 		root = document.documentElement;
8537 
8538 	form.innerHTML = "<a name='" + id + "'/>";
8539 
8540 	// Inject it into the root element, check its status, and remove it quickly
8541 	root.insertBefore( form, root.firstChild );
8542 
8543 	// The workaround has to do additional checks after a getElementById
8544 	// Which slows things down for other browsers (hence the branching)
8545 	if ( document.getElementById( id ) ) {
8546 		Expr.find.ID = function( match, context, isXML ) {
8547 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
8548 				var m = context.getElementById(match[1]);
8549 
8550 				return m ?
8551 					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
8552 						[m] :
8553 						undefined :
8554 					[];
8555 			}
8556 		};
8557 
8558 		Expr.filter.ID = function( elem, match ) {
8559 			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
8560 
8561 			return elem.nodeType === 1 && node && node.nodeValue === match;
8562 		};
8563 	}
8564 
8565 	root.removeChild( form );
8566 
8567 	// release memory in IE
8568 	root = form = null;
8569 })();
8570 
8571 (function(){
8572 	// Check to see if the browser returns only elements
8573 	// when doing getElementsByTagName("*")
8574 
8575 	// Create a fake element
8576 	var div = document.createElement("div");
8577 	div.appendChild( document.createComment("") );
8578 
8579 	// Make sure no comments are found
8580 	if ( div.getElementsByTagName("*").length > 0 ) {
8581 		Expr.find.TAG = function( match, context ) {
8582 			var results = context.getElementsByTagName( match[1] );
8583 
8584 			// Filter out possible comments
8585 			if ( match[1] === "*" ) {
8586 				var tmp = [];
8587 
8588 				for ( var i = 0; results[i]; i++ ) {
8589 					if ( results[i].nodeType === 1 ) {
8590 						tmp.push( results[i] );
8591 					}
8592 				}
8593 
8594 				results = tmp;
8595 			}
8596 
8597 			return results;
8598 		};
8599 	}
8600 
8601 	// Check to see if an attribute returns normalized href attributes
8602 	div.innerHTML = "<a href='#'></a>";
8603 
8604 	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
8605 			div.firstChild.getAttribute("href") !== "#" ) {
8606 
8607 		Expr.attrHandle.href = function( elem ) {
8608 			return elem.getAttribute( "href", 2 );
8609 		};
8610 	}
8611 
8612 	// release memory in IE
8613 	div = null;
8614 })();
8615 
8616 if ( document.querySelectorAll ) {
8617 	(function(){
8618 		var oldSizzle = Sizzle,
8619 			div = document.createElement("div"),
8620 			id = "__sizzle__";
8621 
8622 		div.innerHTML = "<p class='TEST'></p>";
8623 
8624 		// Safari can't handle uppercase or unicode characters when
8625 		// in quirks mode.
8626 		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
8627 			return;
8628 		}
8629 
8630 		Sizzle = function( query, context, extra, seed ) {
8631 			context = context || document;
8632 
8633 			// Only use querySelectorAll on non-XML documents
8634 			// (ID selectors don't work in non-HTML documents)
8635 			if ( !seed && !Sizzle.isXML(context) ) {
8636 				// See if we find a selector to speed up
8637 				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
8638 
8639 				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
8640 					// Speed-up: Sizzle("TAG")
8641 					if ( match[1] ) {
8642 						return makeArray( context.getElementsByTagName( query ), extra );
8643 
8644 					// Speed-up: Sizzle(".CLASS")
8645 					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
8646 						return makeArray( context.getElementsByClassName( match[2] ), extra );
8647 					}
8648 				}
8649 
8650 				if ( context.nodeType === 9 ) {
8651 					// Speed-up: Sizzle("body")
8652 					// The body element only exists once, optimize finding it
8653 					if ( query === "body" && context.body ) {
8654 						return makeArray( [ context.body ], extra );
8655 
8656 					// Speed-up: Sizzle("#ID")
8657 					} else if ( match && match[3] ) {
8658 						var elem = context.getElementById( match[3] );
8659 
8660 						// Check parentNode to catch when Blackberry 4.6 returns
8661 						// nodes that are no longer in the document #6963
8662 						if ( elem && elem.parentNode ) {
8663 							// Handle the case where IE and Opera return items
8664 							// by name instead of ID
8665 							if ( elem.id === match[3] ) {
8666 								return makeArray( [ elem ], extra );
8667 							}
8668 
8669 						} else {
8670 							return makeArray( [], extra );
8671 						}
8672 					}
8673 
8674 					try {
8675 						return makeArray( context.querySelectorAll(query), extra );
8676 					} catch(qsaError) {}
8677 
8678 				// qSA works strangely on Element-rooted queries
8679 				// We can work around this by specifying an extra ID on the root
8680 				// and working up from there (Thanks to Andrew Dupont for the technique)
8681 				// IE 8 doesn't work on object elements
8682 				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
8683 					var oldContext = context,
8684 						old = context.getAttribute( "id" ),
8685 						nid = old || id,
8686 						hasParent = context.parentNode,
8687 						relativeHierarchySelector = /^\s*[+~]/.test( query );
8688 
8689 					if ( !old ) {
8690 						context.setAttribute( "id", nid );
8691 					} else {
8692 						nid = nid.replace( /'/g, "\\$&" );
8693 					}
8694 					if ( relativeHierarchySelector && hasParent ) {
8695 						context = context.parentNode;
8696 					}
8697 
8698 					try {
8699 						if ( !relativeHierarchySelector || hasParent ) {
8700 							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
8701 						}
8702 
8703 					} catch(pseudoError) {
8704 					} finally {
8705 						if ( !old ) {
8706 							oldContext.removeAttribute( "id" );
8707 						}
8708 					}
8709 				}
8710 			}
8711 
8712 			return oldSizzle(query, context, extra, seed);
8713 		};
8714 
8715 		for ( var prop in oldSizzle ) {
8716 			Sizzle[ prop ] = oldSizzle[ prop ];
8717 		}
8718 
8719 		// release memory in IE
8720 		div = null;
8721 	})();
8722 }
8723 
8724 (function(){
8725 	var html = document.documentElement,
8726 		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
8727 
8728 	if ( matches ) {
8729 		// Check to see if it's possible to do matchesSelector
8730 		// on a disconnected node (IE 9 fails this)
8731 		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
8732 			pseudoWorks = false;
8733 
8734 		try {
8735 			// This should fail with an exception
8736 			// Gecko does not error, returns false instead
8737 			matches.call( document.documentElement, "[test!='']:sizzle" );
8738 
8739 		} catch( pseudoError ) {
8740 			pseudoWorks = true;
8741 		}
8742 
8743 		Sizzle.matchesSelector = function( node, expr ) {
8744 			// Make sure that attribute selectors are quoted
8745 			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
8746 
8747 			if ( !Sizzle.isXML( node ) ) {
8748 				try {
8749 					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
8750 						var ret = matches.call( node, expr );
8751 
8752 						// IE 9's matchesSelector returns false on disconnected nodes
8753 						if ( ret || !disconnectedMatch ||
8754 								// As well, disconnected nodes are said to be in a document
8755 								// fragment in IE 9, so check for that
8756 								node.document && node.document.nodeType !== 11 ) {
8757 							return ret;
8758 						}
8759 					}
8760 				} catch(e) {}
8761 			}
8762 
8763 			return Sizzle(expr, null, null, [node]).length > 0;
8764 		};
8765 	}
8766 })();
8767 
8768 (function(){
8769 	var div = document.createElement("div");
8770 
8771 	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
8772 
8773 	// Opera can't find a second classname (in 9.6)
8774 	// Also, make sure that getElementsByClassName actually exists
8775 	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
8776 		return;
8777 	}
8778 
8779 	// Safari caches class attributes, doesn't catch changes (in 3.2)
8780 	div.lastChild.className = "e";
8781 
8782 	if ( div.getElementsByClassName("e").length === 1 ) {
8783 		return;
8784 	}
8785 
8786 	Expr.order.splice(1, 0, "CLASS");
8787 	Expr.find.CLASS = function( match, context, isXML ) {
8788 		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
8789 			return context.getElementsByClassName(match[1]);
8790 		}
8791 	};
8792 
8793 	// release memory in IE
8794 	div = null;
8795 })();
8796 
8797 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8798 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8799 		var elem = checkSet[i];
8800 
8801 		if ( elem ) {
8802 			var match = false;
8803 
8804 			elem = elem[dir];
8805 
8806 			while ( elem ) {
8807 				if ( elem[ expando ] === doneName ) {
8808 					match = checkSet[elem.sizset];
8809 					break;
8810 				}
8811 
8812 				if ( elem.nodeType === 1 && !isXML ){
8813 					elem[ expando ] = doneName;
8814 					elem.sizset = i;
8815 				}
8816 
8817 				if ( elem.nodeName.toLowerCase() === cur ) {
8818 					match = elem;
8819 					break;
8820 				}
8821 
8822 				elem = elem[dir];
8823 			}
8824 
8825 			checkSet[i] = match;
8826 		}
8827 	}
8828 }
8829 
8830 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8831 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8832 		var elem = checkSet[i];
8833 
8834 		if ( elem ) {
8835 			var match = false;
8836 
8837 			elem = elem[dir];
8838 
8839 			while ( elem ) {
8840 				if ( elem[ expando ] === doneName ) {
8841 					match = checkSet[elem.sizset];
8842 					break;
8843 				}
8844 
8845 				if ( elem.nodeType === 1 ) {
8846 					if ( !isXML ) {
8847 						elem[ expando ] = doneName;
8848 						elem.sizset = i;
8849 					}
8850 
8851 					if ( typeof cur !== "string" ) {
8852 						if ( elem === cur ) {
8853 							match = true;
8854 							break;
8855 						}
8856 
8857 					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
8858 						match = elem;
8859 						break;
8860 					}
8861 				}
8862 
8863 				elem = elem[dir];
8864 			}
8865 
8866 			checkSet[i] = match;
8867 		}
8868 	}
8869 }
8870 
8871 if ( document.documentElement.contains ) {
8872 	Sizzle.contains = function( a, b ) {
8873 		return a !== b && (a.contains ? a.contains(b) : true);
8874 	};
8875 
8876 } else if ( document.documentElement.compareDocumentPosition ) {
8877 	Sizzle.contains = function( a, b ) {
8878 		return !!(a.compareDocumentPosition(b) & 16);
8879 	};
8880 
8881 } else {
8882 	Sizzle.contains = function() {
8883 		return false;
8884 	};
8885 }
8886 
8887 Sizzle.isXML = function( elem ) {
8888 	// documentElement is verified for cases where it doesn't yet exist
8889 	// (such as loading iframes in IE - #4833)
8890 	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
8891 
8892 	return documentElement ? documentElement.nodeName !== "HTML" : false;
8893 };
8894 
8895 var posProcess = function( selector, context, seed ) {
8896 	var match,
8897 		tmpSet = [],
8898 		later = "",
8899 		root = context.nodeType ? [context] : context;
8900 
8901 	// Position selectors must be done after the filter
8902 	// And so must :not(positional) so we move all PSEUDOs to the end
8903 	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
8904 		later += match[0];
8905 		selector = selector.replace( Expr.match.PSEUDO, "" );
8906 	}
8907 
8908 	selector = Expr.relative[selector] ? selector + "*" : selector;
8909 
8910 	for ( var i = 0, l = root.length; i < l; i++ ) {
8911 		Sizzle( selector, root[i], tmpSet, seed );
8912 	}
8913 
8914 	return Sizzle.filter( later, tmpSet );
8915 };
8916 
8917 // EXPOSE
8918 
8919 window.tinymce.dom.Sizzle = Sizzle;
8920 
8921 })();
8922 
8923 
8924 (function(tinymce) {
8925 	tinymce.dom.Element = function(id, settings) {
8926 		var t = this, dom, el;
8927 
8928 		t.settings = settings = settings || {};
8929 		t.id = id;
8930 		t.dom = dom = settings.dom || tinymce.DOM;
8931 
8932 		// Only IE leaks DOM references, this is a lot faster
8933 		if (!tinymce.isIE)
8934 			el = dom.get(t.id);
8935 
8936 		tinymce.each(
8937 				('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
8938 				'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
8939 				'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
8940 				'isHidden,setHTML,get').split(/,/), function(k) {
8941 					t[k] = function() {
8942 						var a = [id], i;
8943 
8944 						for (i = 0; i < arguments.length; i++)
8945 							a.push(arguments[i]);
8946 
8947 						a = dom[k].apply(dom, a);
8948 						t.update(k);
8949 
8950 						return a;
8951 					};
8952 			}
8953 		);
8954 
8955 		tinymce.extend(t, {
8956 			on : function(n, f, s) {
8957 				return tinymce.dom.Event.add(t.id, n, f, s);
8958 			},
8959 
8960 			getXY : function() {
8961 				return {
8962 					x : parseInt(t.getStyle('left')),
8963 					y : parseInt(t.getStyle('top'))
8964 				};
8965 			},
8966 
8967 			getSize : function() {
8968 				var n = dom.get(t.id);
8969 
8970 				return {
8971 					w : parseInt(t.getStyle('width') || n.clientWidth),
8972 					h : parseInt(t.getStyle('height') || n.clientHeight)
8973 				};
8974 			},
8975 
8976 			moveTo : function(x, y) {
8977 				t.setStyles({left : x, top : y});
8978 			},
8979 
8980 			moveBy : function(x, y) {
8981 				var p = t.getXY();
8982 
8983 				t.moveTo(p.x + x, p.y + y);
8984 			},
8985 
8986 			resizeTo : function(w, h) {
8987 				t.setStyles({width : w, height : h});
8988 			},
8989 
8990 			resizeBy : function(w, h) {
8991 				var s = t.getSize();
8992 
8993 				t.resizeTo(s.w + w, s.h + h);
8994 			},
8995 
8996 			update : function(k) {
8997 				var b;
8998 
8999 				if (tinymce.isIE6 && settings.blocker) {
9000 					k = k || '';
9001 
9002 					// Ignore getters
9003 					if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
9004 						return;
9005 
9006 					// Remove blocker on remove
9007 					if (k == 'remove') {
9008 						dom.remove(t.blocker);
9009 						return;
9010 					}
9011 
9012 					if (!t.blocker) {
9013 						t.blocker = dom.uniqueId();
9014 						b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
9015 						dom.setStyle(b, 'opacity', 0);
9016 					} else
9017 						b = dom.get(t.blocker);
9018 
9019 					dom.setStyles(b, {
9020 						left : t.getStyle('left', 1),
9021 						top : t.getStyle('top', 1),
9022 						width : t.getStyle('width', 1),
9023 						height : t.getStyle('height', 1),
9024 						display : t.getStyle('display', 1),
9025 						zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
9026 					});
9027 				}
9028 			}
9029 		});
9030 	};
9031 })(tinymce);
9032 
9033 (function(tinymce) {
9034 	function trimNl(s) {
9035 		return s.replace(/[\n\r]+/g, '');
9036 	};
9037 
9038 	// Shorten names
9039 	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
9040 
9041 	tinymce.create('tinymce.dom.Selection', {
9042 		Selection : function(dom, win, serializer, editor) {
9043 			var t = this;
9044 
9045 			t.dom = dom;
9046 			t.win = win;
9047 			t.serializer = serializer;
9048 			t.editor = editor;
9049 
9050 			// Add events
9051 			each([
9052 				'onBeforeSetContent',
9053 
9054 				'onBeforeGetContent',
9055 
9056 				'onSetContent',
9057 
9058 				'onGetContent'
9059 			], function(e) {
9060 				t[e] = new tinymce.util.Dispatcher(t);
9061 			});
9062 
9063 			// No W3C Range support
9064 			if (!t.win.getSelection)
9065 				t.tridentSel = new tinymce.dom.TridentSelection(t);
9066 
9067 			if (tinymce.isIE && dom.boxModel)
9068 				this._fixIESelection();
9069 
9070 			// Prevent leaks
9071 			tinymce.addUnload(t.destroy, t);
9072 		},
9073 
9074 		setCursorLocation: function(node, offset) {
9075 			var t = this; var r = t.dom.createRng();
9076 			r.setStart(node, offset);
9077 			r.setEnd(node, offset);
9078 			t.setRng(r);
9079 			t.collapse(false);
9080 		},
9081 		getContent : function(s) {
9082 			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
9083 
9084 			s = s || {};
9085 			wb = wa = '';
9086 			s.get = true;
9087 			s.format = s.format || 'html';
9088 			s.forced_root_block = '';
9089 			t.onBeforeGetContent.dispatch(t, s);
9090 
9091 			if (s.format == 'text')
9092 				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
9093 
9094 			if (r.cloneContents) {
9095 				n = r.cloneContents();
9096 
9097 				if (n)
9098 					e.appendChild(n);
9099 			} else if (is(r.item) || is(r.htmlText)) {
9100 				// IE will produce invalid markup if elements are present that
9101 				// it doesn't understand like custom elements or HTML5 elements.
9102 				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
9103 				e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
9104 				e.removeChild(e.firstChild);
9105 			} else
9106 				e.innerHTML = r.toString();
9107 
9108 			// Keep whitespace before and after
9109 			if (/^\s/.test(e.innerHTML))
9110 				wb = ' ';
9111 
9112 			if (/\s+$/.test(e.innerHTML))
9113 				wa = ' ';
9114 
9115 			s.getInner = true;
9116 
9117 			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
9118 			t.onGetContent.dispatch(t, s);
9119 
9120 			return s.content;
9121 		},
9122 
9123 		setContent : function(content, args) {
9124 			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
9125 
9126 			args = args || {format : 'html'};
9127 			args.set = true;
9128 			content = args.content = content;
9129 
9130 			// Dispatch before set content event
9131 			if (!args.no_events)
9132 				self.onBeforeSetContent.dispatch(self, args);
9133 
9134 			content = args.content;
9135 
9136 			if (rng.insertNode) {
9137 				// Make caret marker since insertNode places the caret in the beginning of text after insert
9138 				content += '<span id="__caret">_</span>';
9139 
9140 				// Delete and insert new node
9141 				if (rng.startContainer == doc && rng.endContainer == doc) {
9142 					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
9143 					doc.body.innerHTML = content;
9144 				} else {
9145 					rng.deleteContents();
9146 
9147 					if (doc.body.childNodes.length === 0) {
9148 						doc.body.innerHTML = content;
9149 					} else {
9150 						// createContextualFragment doesn't exists in IE 9 DOMRanges
9151 						if (rng.createContextualFragment) {
9152 							rng.insertNode(rng.createContextualFragment(content));
9153 						} else {
9154 							// Fake createContextualFragment call in IE 9
9155 							frag = doc.createDocumentFragment();
9156 							temp = doc.createElement('div');
9157 
9158 							frag.appendChild(temp);
9159 							temp.outerHTML = content;
9160 
9161 							rng.insertNode(frag);
9162 						}
9163 					}
9164 				}
9165 
9166 				// Move to caret marker
9167 				caretNode = self.dom.get('__caret');
9168 
9169 				// Make sure we wrap it compleatly, Opera fails with a simple select call
9170 				rng = doc.createRange();
9171 				rng.setStartBefore(caretNode);
9172 				rng.setEndBefore(caretNode);
9173 				self.setRng(rng);
9174 
9175 				// Remove the caret position
9176 				self.dom.remove('__caret');
9177 
9178 				try {
9179 					self.setRng(rng);
9180 				} catch (ex) {
9181 					// Might fail on Opera for some odd reason
9182 				}
9183 			} else {
9184 				if (rng.item) {
9185 					// Delete content and get caret text selection
9186 					doc.execCommand('Delete', false, null);
9187 					rng = self.getRng();
9188 				}
9189 
9190 				// Explorer removes spaces from the beginning of pasted contents
9191 				if (/^\s+/.test(content)) {
9192 					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
9193 					self.dom.remove('__mce_tmp');
9194 				} else
9195 					rng.pasteHTML(content);
9196 			}
9197 
9198 			// Dispatch set content event
9199 			if (!args.no_events)
9200 				self.onSetContent.dispatch(self, args);
9201 		},
9202 
9203 		getStart : function() {
9204 			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
9205 
9206 			if (rng.duplicate || rng.item) {
9207 				// Control selection, return first item
9208 				if (rng.item)
9209 					return rng.item(0);
9210 
9211 				// Get start element
9212 				checkRng = rng.duplicate();
9213 				checkRng.collapse(1);
9214 				startElement = checkRng.parentElement();
9215 				if (startElement.ownerDocument !== self.dom.doc) {
9216 					startElement = self.dom.getRoot();
9217 				}
9218 
9219 				// Check if range parent is inside the start element, then return the inner parent element
9220 				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
9221 				parentElement = node = rng.parentElement();
9222 				while (node = node.parentNode) {
9223 					if (node == startElement) {
9224 						startElement = parentElement;
9225 						break;
9226 					}
9227 				}
9228 
9229 				return startElement;
9230 			} else {
9231 				startElement = rng.startContainer;
9232 
9233 				if (startElement.nodeType == 1 && startElement.hasChildNodes())
9234 					startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
9235 
9236 				if (startElement && startElement.nodeType == 3)
9237 					return startElement.parentNode;
9238 
9239 				return startElement;
9240 			}
9241 		},
9242 
9243 		getEnd : function() {
9244 			var self = this, rng = self.getRng(), endElement, endOffset;
9245 
9246 			if (rng.duplicate || rng.item) {
9247 				if (rng.item)
9248 					return rng.item(0);
9249 
9250 				rng = rng.duplicate();
9251 				rng.collapse(0);
9252 				endElement = rng.parentElement();
9253 				if (endElement.ownerDocument !== self.dom.doc) {
9254 					endElement = self.dom.getRoot();
9255 				}
9256 
9257 				if (endElement && endElement.nodeName == 'BODY')
9258 					return endElement.lastChild || endElement;
9259 
9260 				return endElement;
9261 			} else {
9262 				endElement = rng.endContainer;
9263 				endOffset = rng.endOffset;
9264 
9265 				if (endElement.nodeType == 1 && endElement.hasChildNodes())
9266 					endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
9267 
9268 				if (endElement && endElement.nodeType == 3)
9269 					return endElement.parentNode;
9270 
9271 				return endElement;
9272 			}
9273 		},
9274 
9275 		getBookmark : function(type, normalized) {
9276 			var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
9277 
9278 			function findIndex(name, element) {
9279 				var index = 0;
9280 
9281 				each(dom.select(name), function(node, i) {
9282 					if (node == element)
9283 						index = i;
9284 				});
9285 
9286 				return index;
9287 			};
9288 
9289 			function normalizeTableCellSelection(rng) {
9290 				function moveEndPoint(start) {
9291 					var container, offset, childNodes, prefix = start ? 'start' : 'end';
9292 
9293 					container = rng[prefix + 'Container'];
9294 					offset = rng[prefix + 'Offset'];
9295 
9296 					if (container.nodeType == 1 && container.nodeName == "TR") {
9297 						childNodes = container.childNodes;
9298 						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9299 						if (container) {
9300 							offset = start ? 0 : container.childNodes.length;
9301 							rng['set' + (start ? 'Start' : 'End')](container, offset);
9302 						}
9303 					}
9304 				};
9305 
9306 				moveEndPoint(true);
9307 				moveEndPoint();
9308 
9309 				return rng;
9310 			};
9311 
9312 			function getLocation() {
9313 				var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
9314 
9315 				function getPoint(rng, start) {
9316 					var container = rng[start ? 'startContainer' : 'endContainer'],
9317 						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
9318 
9319 					if (container.nodeType == 3) {
9320 						if (normalized) {
9321 							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
9322 								offset += node.nodeValue.length;
9323 						}
9324 
9325 						point.push(offset);
9326 					} else {
9327 						childNodes = container.childNodes;
9328 
9329 						if (offset >= childNodes.length && childNodes.length) {
9330 							after = 1;
9331 							offset = Math.max(0, childNodes.length - 1);
9332 						}
9333 
9334 						point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
9335 					}
9336 
9337 					for (; container && container != root; container = container.parentNode)
9338 						point.push(t.dom.nodeIndex(container, normalized));
9339 
9340 					return point;
9341 				};
9342 
9343 				bookmark.start = getPoint(rng, true);
9344 
9345 				if (!t.isCollapsed())
9346 					bookmark.end = getPoint(rng);
9347 
9348 				return bookmark;
9349 			};
9350 
9351 			if (type == 2) {
9352 				if (t.tridentSel)
9353 					return t.tridentSel.getBookmark(type);
9354 
9355 				return getLocation();
9356 			}
9357 
9358 			// Handle simple range
9359 			if (type)
9360 				return {rng : t.getRng()};
9361 
9362 			rng = t.getRng();
9363 			id = dom.uniqueId();
9364 			collapsed = tinyMCE.activeEditor.selection.isCollapsed();
9365 			styles = 'overflow:hidden;line-height:0px';
9366 
9367 			// Explorer method
9368 			if (rng.duplicate || rng.item) {
9369 				// Text selection
9370 				if (!rng.item) {
9371 					rng2 = rng.duplicate();
9372 
9373 					try {
9374 						// Insert start marker
9375 						rng.collapse();
9376 						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
9377 
9378 						// Insert end marker
9379 						if (!collapsed) {
9380 							rng2.collapse(false);
9381 
9382 							// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
9383 							rng.moveToElementText(rng2.parentElement());
9384 							if (rng.compareEndPoints('StartToEnd', rng2) === 0)
9385 								rng2.move('character', -1);
9386 
9387 							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
9388 						}
9389 					} catch (ex) {
9390 						// IE might throw unspecified error so lets ignore it
9391 						return null;
9392 					}
9393 				} else {
9394 					// Control selection
9395 					element = rng.item(0);
9396 					name = element.nodeName;
9397 
9398 					return {name : name, index : findIndex(name, element)};
9399 				}
9400 			} else {
9401 				element = t.getNode();
9402 				name = element.nodeName;
9403 				if (name == 'IMG')
9404 					return {name : name, index : findIndex(name, element)};
9405 
9406 				// W3C method
9407 				rng2 = normalizeTableCellSelection(rng.cloneRange());
9408 
9409 				// Insert end marker
9410 				if (!collapsed) {
9411 					rng2.collapse(false);
9412 					rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
9413 				}
9414 
9415 				rng = normalizeTableCellSelection(rng);
9416 				rng.collapse(true);
9417 				rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
9418 			}
9419 
9420 			t.moveToBookmark({id : id, keep : 1});
9421 
9422 			return {id : id};
9423 		},
9424 
9425 		moveToBookmark : function(bookmark) {
9426 			var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
9427 
9428 			function setEndPoint(start) {
9429 				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
9430 
9431 				if (point) {
9432 					offset = point[0];
9433 
9434 					// Find container node
9435 					for (node = root, i = point.length - 1; i >= 1; i--) {
9436 						children = node.childNodes;
9437 
9438 						if (point[i] > children.length - 1)
9439 							return;
9440 
9441 						node = children[point[i]];
9442 					}
9443 
9444 					// Move text offset to best suitable location
9445 					if (node.nodeType === 3)
9446 						offset = Math.min(point[0], node.nodeValue.length);
9447 
9448 					// Move element offset to best suitable location
9449 					if (node.nodeType === 1)
9450 						offset = Math.min(point[0], node.childNodes.length);
9451 
9452 					// Set offset within container node
9453 					if (start)
9454 						rng.setStart(node, offset);
9455 					else
9456 						rng.setEnd(node, offset);
9457 				}
9458 
9459 				return true;
9460 			};
9461 
9462 			function restoreEndPoint(suffix) {
9463 				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
9464 
9465 				if (marker) {
9466 					node = marker.parentNode;
9467 
9468 					if (suffix == 'start') {
9469 						if (!keep) {
9470 							idx = dom.nodeIndex(marker);
9471 						} else {
9472 							node = marker.firstChild;
9473 							idx = 1;
9474 						}
9475 
9476 						startContainer = endContainer = node;
9477 						startOffset = endOffset = idx;
9478 					} else {
9479 						if (!keep) {
9480 							idx = dom.nodeIndex(marker);
9481 						} else {
9482 							node = marker.firstChild;
9483 							idx = 1;
9484 						}
9485 
9486 						endContainer = node;
9487 						endOffset = idx;
9488 					}
9489 
9490 					if (!keep) {
9491 						prev = marker.previousSibling;
9492 						next = marker.nextSibling;
9493 
9494 						// Remove all marker text nodes
9495 						each(tinymce.grep(marker.childNodes), function(node) {
9496 							if (node.nodeType == 3)
9497 								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
9498 						});
9499 
9500 						// Remove marker but keep children if for example contents where inserted into the marker
9501 						// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
9502 						while (marker = dom.get(bookmark.id + '_' + suffix))
9503 							dom.remove(marker, 1);
9504 
9505 						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
9506 						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
9507 						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
9508 							idx = prev.nodeValue.length;
9509 							prev.appendData(next.nodeValue);
9510 							dom.remove(next);
9511 
9512 							if (suffix == 'start') {
9513 								startContainer = endContainer = prev;
9514 								startOffset = endOffset = idx;
9515 							} else {
9516 								endContainer = prev;
9517 								endOffset = idx;
9518 							}
9519 						}
9520 					}
9521 				}
9522 			};
9523 
9524 			function addBogus(node) {
9525 				// Adds a bogus BR element for empty block elements
9526 				if (dom.isBlock(node) && !node.innerHTML && !isIE)
9527 					node.innerHTML = '<br data-mce-bogus="1" />';
9528 
9529 				return node;
9530 			};
9531 
9532 			if (bookmark) {
9533 				if (bookmark.start) {
9534 					rng = dom.createRng();
9535 					root = dom.getRoot();
9536 
9537 					if (t.tridentSel)
9538 						return t.tridentSel.moveToBookmark(bookmark);
9539 
9540 					if (setEndPoint(true) && setEndPoint()) {
9541 						t.setRng(rng);
9542 					}
9543 				} else if (bookmark.id) {
9544 					// Restore start/end points
9545 					restoreEndPoint('start');
9546 					restoreEndPoint('end');
9547 
9548 					if (startContainer) {
9549 						rng = dom.createRng();
9550 						rng.setStart(addBogus(startContainer), startOffset);
9551 						rng.setEnd(addBogus(endContainer), endOffset);
9552 						t.setRng(rng);
9553 					}
9554 				} else if (bookmark.name) {
9555 					t.select(dom.select(bookmark.name)[bookmark.index]);
9556 				} else if (bookmark.rng)
9557 					t.setRng(bookmark.rng);
9558 			}
9559 		},
9560 
9561 		select : function(node, content) {
9562 			var t = this, dom = t.dom, rng = dom.createRng(), idx;
9563 
9564 			function setPoint(node, start) {
9565 				var walker = new TreeWalker(node, node);
9566 
9567 				do {
9568 					// Text node
9569 					if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
9570 						if (start)
9571 							rng.setStart(node, 0);
9572 						else
9573 							rng.setEnd(node, node.nodeValue.length);
9574 
9575 						return;
9576 					}
9577 
9578 					// BR element
9579 					if (node.nodeName == 'BR') {
9580 						if (start)
9581 							rng.setStartBefore(node);
9582 						else
9583 							rng.setEndBefore(node);
9584 
9585 						return;
9586 					}
9587 				} while (node = (start ? walker.next() : walker.prev()));
9588 			};
9589 
9590 			if (node) {
9591 				idx = dom.nodeIndex(node);
9592 				rng.setStart(node.parentNode, idx);
9593 				rng.setEnd(node.parentNode, idx + 1);
9594 
9595 				// Find first/last text node or BR element
9596 				if (content) {
9597 					setPoint(node, 1);
9598 					setPoint(node);
9599 				}
9600 
9601 				t.setRng(rng);
9602 			}
9603 
9604 			return node;
9605 		},
9606 
9607 		isCollapsed : function() {
9608 			var t = this, r = t.getRng(), s = t.getSel();
9609 
9610 			if (!r || r.item)
9611 				return false;
9612 
9613 			if (r.compareEndPoints)
9614 				return r.compareEndPoints('StartToEnd', r) === 0;
9615 
9616 			return !s || r.collapsed;
9617 		},
9618 
9619 		collapse : function(to_start) {
9620 			var self = this, rng = self.getRng(), node;
9621 
9622 			// Control range on IE
9623 			if (rng.item) {
9624 				node = rng.item(0);
9625 				rng = self.win.document.body.createTextRange();
9626 				rng.moveToElementText(node);
9627 			}
9628 
9629 			rng.collapse(!!to_start);
9630 			self.setRng(rng);
9631 		},
9632 
9633 		getSel : function() {
9634 			var t = this, w = this.win;
9635 
9636 			return w.getSelection ? w.getSelection() : w.document.selection;
9637 		},
9638 
9639 		getRng : function(w3c) {
9640 			var self = this, selection, rng, elm, doc = self.win.document;
9641 
9642 			// Found tridentSel object then we need to use that one
9643 			if (w3c && self.tridentSel) {
9644 				return self.tridentSel.getRangeAt(0);
9645 			}
9646 
9647 			try {
9648 				if (selection = self.getSel()) {
9649 					rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
9650 				}
9651 			} catch (ex) {
9652 				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
9653 			}
9654 
9655 			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
9656 			if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {
9657 				elm = doc.selection.createRange().item(0);
9658 				rng = doc.createRange();
9659 				rng.setStartBefore(elm);
9660 				rng.setEndAfter(elm);
9661 			}
9662 
9663 			// No range found then create an empty one
9664 			// This can occur when the editor is placed in a hidden container element on Gecko
9665 			// Or on IE when there was an exception
9666 			if (!rng) {
9667 				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
9668 			}
9669 
9670 			// If range is at start of document then move it to start of body
9671 			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
9672 				elm = self.dom.getRoot();
9673 				rng.setStart(elm, 0);
9674 				rng.setEnd(elm, 0);
9675 			}
9676 
9677 			if (self.selectedRange && self.explicitRange) {
9678 				if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
9679 					// Safari, Opera and Chrome only ever select text which causes the range to change.
9680 					// This lets us use the originally set range if the selection hasn't been changed by the user.
9681 					rng = self.explicitRange;
9682 				} else {
9683 					self.selectedRange = null;
9684 					self.explicitRange = null;
9685 				}
9686 			}
9687 
9688 			return rng;
9689 		},
9690 
9691 		setRng : function(r, forward) {
9692 			var s, t = this;
9693 
9694 			if (!t.tridentSel) {
9695 				s = t.getSel();
9696 
9697 				if (s) {
9698 					t.explicitRange = r;
9699 
9700 					try {
9701 						s.removeAllRanges();
9702 					} catch (ex) {
9703 						// IE9 might throw errors here don't know why
9704 					}
9705 
9706 					s.addRange(r);
9707 
9708 					// Forward is set to false and we have an extend function
9709 					if (forward === false && s.extend) {
9710 						s.collapse(r.endContainer, r.endOffset);
9711 						s.extend(r.startContainer, r.startOffset);
9712 					}
9713 
9714 					// adding range isn't always successful so we need to check range count otherwise an exception can occur
9715 					t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
9716 				}
9717 			} else {
9718 				// Is W3C Range
9719 				if (r.cloneRange) {
9720 					try {
9721 						t.tridentSel.addRange(r);
9722 						return;
9723 					} catch (ex) {
9724 						//IE9 throws an error here if called before selection is placed in the editor
9725 					}
9726 				}
9727 
9728 				// Is IE specific range
9729 				try {
9730 					r.select();
9731 				} catch (ex) {
9732 					// Needed for some odd IE bug #1843306
9733 				}
9734 			}
9735 		},
9736 
9737 		setNode : function(n) {
9738 			var t = this;
9739 
9740 			t.setContent(t.dom.getOuterHTML(n));
9741 
9742 			return n;
9743 		},
9744 
9745 		getNode : function() {
9746 			var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
9747 
9748 			function skipEmptyTextNodes(n, forwards) {
9749 				var orig = n;
9750 				while (n && n.nodeType === 3 && n.length === 0) {
9751 					n = forwards ? n.nextSibling : n.previousSibling;
9752 				}
9753 				return n || orig;
9754 			};
9755 
9756 			// Range maybe lost after the editor is made visible again
9757 			if (!rng)
9758 				return t.dom.getRoot();
9759 
9760 			if (rng.setStart) {
9761 				elm = rng.commonAncestorContainer;
9762 
9763 				// Handle selection a image or other control like element such as anchors
9764 				if (!rng.collapsed) {
9765 					if (rng.startContainer == rng.endContainer) {
9766 						if (rng.endOffset - rng.startOffset < 2) {
9767 							if (rng.startContainer.hasChildNodes())
9768 								elm = rng.startContainer.childNodes[rng.startOffset];
9769 						}
9770 					}
9771 
9772 					// If the anchor node is a element instead of a text node then return this element
9773 					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
9774 					//	return sel.anchorNode.childNodes[sel.anchorOffset];
9775 
9776 					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
9777 					// This happens when you double click an underlined word in FireFox.
9778 					if (start.nodeType === 3 && end.nodeType === 3) {
9779 						if (start.length === rng.startOffset) {
9780 							start = skipEmptyTextNodes(start.nextSibling, true);
9781 						} else {
9782 							start = start.parentNode;
9783 						}
9784 						if (rng.endOffset === 0) {
9785 							end = skipEmptyTextNodes(end.previousSibling, false);
9786 						} else {
9787 							end = end.parentNode;
9788 						}
9789 
9790 						if (start && start === end)
9791 							return start;
9792 					}
9793 				}
9794 
9795 				if (elm && elm.nodeType == 3)
9796 					return elm.parentNode;
9797 
9798 				return elm;
9799 			}
9800 
9801 			return rng.item ? rng.item(0) : rng.parentElement();
9802 		},
9803 
9804 		getSelectedBlocks : function(st, en) {
9805 			var t = this, dom = t.dom, sb, eb, n, bl = [];
9806 
9807 			sb = dom.getParent(st || t.getStart(), dom.isBlock);
9808 			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
9809 
9810 			if (sb)
9811 				bl.push(sb);
9812 
9813 			if (sb && eb && sb != eb) {
9814 				n = sb;
9815 
9816 				var walker = new TreeWalker(sb, dom.getRoot());
9817 				while ((n = walker.next()) && n != eb) {
9818 					if (dom.isBlock(n))
9819 						bl.push(n);
9820 				}
9821 			}
9822 
9823 			if (eb && sb != eb)
9824 				bl.push(eb);
9825 
9826 			return bl;
9827 		},
9828 
9829 		isForward: function(){
9830 			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
9831 
9832 			// No support for selection direction then always return true
9833 			if (!sel || sel.anchorNode == null || sel.focusNode == null) {
9834 				return true;
9835 			}
9836 
9837 			anchorRange = dom.createRng();
9838 			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
9839 			anchorRange.collapse(true);
9840 
9841 			focusRange = dom.createRng();
9842 			focusRange.setStart(sel.focusNode, sel.focusOffset);
9843 			focusRange.collapse(true);
9844 
9845 			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
9846 		},
9847 
9848 		normalize : function() {
9849 			var self = this, rng, normalized, collapsed, node, sibling;
9850 
9851 			function normalizeEndPoint(start) {
9852 				var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
9853 
9854 				function hasBrBeforeAfter(node, left) {
9855 					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
9856 
9857 					while (node = walker[left ? 'prev' : 'next']()) {
9858 						if (node.nodeName === "BR") {
9859 							return true;
9860 						}
9861 					}
9862 				};
9863 
9864 				// Walks the dom left/right to find a suitable text node to move the endpoint into
9865 				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
9866 				function findTextNodeRelative(left, startNode) {
9867 					var walker, lastInlineElement;
9868 
9869 					startNode = startNode || container;
9870 					walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
9871 
9872 					// Walk left until we hit a text node we can move to or a block/br/img
9873 					while (node = walker[left ? 'prev' : 'next']()) {
9874 						// Found text node that has a length
9875 						if (node.nodeType === 3 && node.nodeValue.length > 0) {
9876 							container = node;
9877 							offset = left ? node.nodeValue.length : 0;
9878 							normalized = true;
9879 							return;
9880 						}
9881 
9882 						// Break if we find a block or a BR/IMG/INPUT etc
9883 						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9884 							return;
9885 						}
9886 
9887 						lastInlineElement = node;
9888 					}
9889 
9890 					// Only fetch the last inline element when in caret mode for now
9891 					if (collapsed && lastInlineElement) {
9892 						container = lastInlineElement;
9893 						normalized = true;
9894 						offset = 0;
9895 					}
9896 				};
9897 
9898 				container = rng[(start ? 'start' : 'end') + 'Container'];
9899 				offset = rng[(start ? 'start' : 'end') + 'Offset'];
9900 				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
9901 
9902 				// If the container is a document move it to the body element
9903 				if (container.nodeType === 9) {
9904 					container = dom.getRoot();
9905 					offset = 0;
9906 				}
9907 
9908 				// If the container is body try move it into the closest text node or position
9909 				if (container === body) {
9910 					// If start is before/after a image, table etc
9911 					if (start) {
9912 						node = container.childNodes[offset > 0 ? offset - 1 : 0];
9913 						if (node) {
9914 							nodeName = node.nodeName.toLowerCase();
9915 							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
9916 								return;
9917 							}
9918 						}
9919 					}
9920 
9921 					// Resolve the index
9922 					if (container.hasChildNodes()) {
9923 						container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
9924 						offset = 0;
9925 
9926 						// Don't walk into elements that doesn't have any child nodes like a IMG
9927 						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
9928 							// Walk the DOM to find a text node to place the caret at or a BR
9929 							node = container;
9930 							walker = new TreeWalker(container, body);
9931 
9932 							do {
9933 								// Found a text node use that position
9934 								if (node.nodeType === 3 && node.nodeValue.length > 0) {
9935 									offset = start ? 0 : node.nodeValue.length;
9936 									container = node;
9937 									normalized = true;
9938 									break;
9939 								}
9940 
9941 								// Found a BR/IMG element that we can place the caret before
9942 								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9943 									offset = dom.nodeIndex(node);
9944 									container = node.parentNode;
9945 
9946 									// Put caret after image when moving the end point
9947 									if (node.nodeName ==  "IMG" && !start) {
9948 										offset++;
9949 									}
9950 
9951 									normalized = true;
9952 									break;
9953 								}
9954 							} while (node = (start ? walker.next() : walker.prev()));
9955 						}
9956 					}
9957 				}
9958 
9959 				// Lean the caret to the left if possible
9960 				if (collapsed) {
9961 					// So this: <b>x</b><i>|x</i>
9962 					// Becomes: <b>x|</b><i>x</i>
9963 					// Seems that only gecko has issues with this
9964 					if (container.nodeType === 3 && offset === 0) {
9965 						findTextNodeRelative(true);
9966 					}
9967 
9968 					// Lean left into empty inline elements when the caret is before a BR
9969 					// So this: <i><b></b><i>|<br></i>
9970 					// Becomes: <i><b>|</b><i><br></i>
9971 					// Seems that only gecko has issues with this
9972 					if (container.nodeType === 1) {
9973 						node = container.childNodes[offset];
9974 						if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
9975 							findTextNodeRelative(true, container.childNodes[offset]);
9976 						}
9977 					}
9978 				}
9979 
9980 				// Lean the start of the selection right if possible
9981 				// So this: x[<b>x]</b>
9982 				// Becomes: x<b>[x]</b>
9983 				if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
9984 					findTextNodeRelative(false);
9985 				}
9986 
9987 				// Set endpoint if it was normalized
9988 				if (normalized)
9989 					rng['set' + (start ? 'Start' : 'End')](container, offset);
9990 			};
9991 
9992 			// Normalize only on non IE browsers for now
9993 			if (tinymce.isIE)
9994 				return;
9995 			
9996 			rng = self.getRng();
9997 			collapsed = rng.collapsed;
9998 
9999 			// Normalize the end points
10000 			normalizeEndPoint(true);
10001 
10002 			if (!collapsed)
10003 				normalizeEndPoint();
10004 
10005 			// Set the selection if it was normalized
10006 			if (normalized) {
10007 				// If it was collapsed then make sure it still is
10008 				if (collapsed) {
10009 					rng.collapse(true);
10010 				}
10011 
10012 				//console.log(self.dom.dumpRng(rng));
10013 				self.setRng(rng, self.isForward());
10014 			}
10015 		},
10016 
10017 		selectorChanged: function(selector, callback) {
10018 			var self = this, currentSelectors;
10019 
10020 			if (!self.selectorChangedData) {
10021 				self.selectorChangedData = {};
10022 				currentSelectors = {};
10023 
10024 				self.editor.onNodeChange.addToTop(function(ed, cm, node) {
10025 					var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
10026 
10027 					// Check for new matching selectors
10028 					each(self.selectorChangedData, function(callbacks, selector) {
10029 						each(parents, function(node) {
10030 							if (dom.is(node, selector)) {
10031 								if (!currentSelectors[selector]) {
10032 									// Execute callbacks
10033 									each(callbacks, function(callback) {
10034 										callback(true, {node: node, selector: selector, parents: parents});
10035 									});
10036 
10037 									currentSelectors[selector] = callbacks;
10038 								}
10039 
10040 								matchedSelectors[selector] = callbacks;
10041 								return false;
10042 							}
10043 						});
10044 					});
10045 
10046 					// Check if current selectors still match
10047 					each(currentSelectors, function(callbacks, selector) {
10048 						if (!matchedSelectors[selector]) {
10049 							delete currentSelectors[selector];
10050 
10051 							each(callbacks, function(callback) {
10052 								callback(false, {node: node, selector: selector, parents: parents});
10053 							});
10054 						}
10055 					});
10056 				});
10057 			}
10058 
10059 			// Add selector listeners
10060 			if (!self.selectorChangedData[selector]) {
10061 				self.selectorChangedData[selector] = [];
10062 			}
10063 
10064 			self.selectorChangedData[selector].push(callback);
10065 
10066 			return self;
10067 		},
10068 
10069 		destroy : function(manual) {
10070 			var self = this;
10071 
10072 			self.win = null;
10073 
10074 			// Manual destroy then remove unload handler
10075 			if (!manual)
10076 				tinymce.removeUnload(self.destroy);
10077 		},
10078 
10079 		// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
10080 		_fixIESelection : function() {
10081 			var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
10082 
10083 			// Return range from point or null if it failed
10084 			function rngFromPoint(x, y) {
10085 				var rng = body.createTextRange();
10086 
10087 				try {
10088 					rng.moveToPoint(x, y);
10089 				} catch (ex) {
10090 					// IE sometimes throws and exception, so lets just ignore it
10091 					rng = null;
10092 				}
10093 
10094 				return rng;
10095 			};
10096 
10097 			// Fires while the selection is changing
10098 			function selectionChange(e) {
10099 				var pointRng;
10100 
10101 				// Check if the button is down or not
10102 				if (e.button) {
10103 					// Create range from mouse position
10104 					pointRng = rngFromPoint(e.x, e.y);
10105 
10106 					if (pointRng) {
10107 						// Check if pointRange is before/after selection then change the endPoint
10108 						if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
10109 							pointRng.setEndPoint('StartToStart', startRng);
10110 						else
10111 							pointRng.setEndPoint('EndToEnd', startRng);
10112 
10113 						pointRng.select();
10114 					}
10115 				} else
10116 					endSelection();
10117 			}
10118 
10119 			// Removes listeners
10120 			function endSelection() {
10121 				var rng = doc.selection.createRange();
10122 
10123 				// If the range is collapsed then use the last start range
10124 				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
10125 					startRng.select();
10126 
10127 				dom.unbind(doc, 'mouseup', endSelection);
10128 				dom.unbind(doc, 'mousemove', selectionChange);
10129 				startRng = started = 0;
10130 			};
10131 
10132 			// Make HTML element unselectable since we are going to handle selection by hand
10133 			doc.documentElement.unselectable = true;
10134 			
10135 			// Detect when user selects outside BODY
10136 			dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
10137 				if (e.target.nodeName === 'HTML') {
10138 					if (started)
10139 						endSelection();
10140 
10141 					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
10142 					htmlElm = doc.documentElement;
10143 					if (htmlElm.scrollHeight > htmlElm.clientHeight)
10144 						return;
10145 
10146 					started = 1;
10147 					// Setup start position
10148 					startRng = rngFromPoint(e.x, e.y);
10149 					if (startRng) {
10150 						// Listen for selection change events
10151 						dom.bind(doc, 'mouseup', endSelection);
10152 						dom.bind(doc, 'mousemove', selectionChange);
10153 
10154 						dom.win.focus();
10155 						startRng.select();
10156 					}
10157 				}
10158 			});
10159 		}
10160 	});
10161 })(tinymce);
10162 
10163 (function(tinymce) {
10164 	tinymce.dom.Serializer = function(settings, dom, schema) {
10165 		var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
10166 
10167 		// Support the old apply_source_formatting option
10168 		if (!settings.apply_source_formatting)
10169 			settings.indent = false;
10170 
10171 		// Default DOM and Schema if they are undefined
10172 		dom = dom || tinymce.DOM;
10173 		schema = schema || new tinymce.html.Schema(settings);
10174 		settings.entity_encoding = settings.entity_encoding || 'named';
10175 		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
10176 
10177 		onPreProcess = new tinymce.util.Dispatcher(self);
10178 
10179 		onPostProcess = new tinymce.util.Dispatcher(self);
10180 
10181 		htmlParser = new tinymce.html.DomParser(settings, schema);
10182 
10183 		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
10184 		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
10185 			var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
10186 
10187 			while (i--) {
10188 				node = nodes[i];
10189 
10190 				value = node.attributes.map[internalName];
10191 				if (value !== undef) {
10192 					// Set external name to internal value and remove internal
10193 					node.attr(name, value.length > 0 ? value : null);
10194 					node.attr(internalName, null);
10195 				} else {
10196 					// No internal attribute found then convert the value we have in the DOM
10197 					value = node.attributes.map[name];
10198 
10199 					if (name === "style")
10200 						value = dom.serializeStyle(dom.parseStyle(value), node.name);
10201 					else if (urlConverter)
10202 						value = urlConverter.call(urlConverterScope, value, name, node.name);
10203 
10204 					node.attr(name, value.length > 0 ? value : null);
10205 				}
10206 			}
10207 		});
10208 
10209 		// Remove internal classes mceItem<..> or mceSelected
10210 		htmlParser.addAttributeFilter('class', function(nodes, name) {
10211 			var i = nodes.length, node, value;
10212 
10213 			while (i--) {
10214 				node = nodes[i];
10215 				value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
10216 				node.attr('class', value.length > 0 ? value : null);
10217 			}
10218 		});
10219 
10220 		// Remove bookmark elements
10221 		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
10222 			var i = nodes.length, node;
10223 
10224 			while (i--) {
10225 				node = nodes[i];
10226 
10227 				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
10228 					node.remove();
10229 			}
10230 		});
10231 
10232 		// Remove expando attributes
10233 		htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
10234 			var i = nodes.length;
10235 
10236 			while (i--) {
10237 				nodes[i].attr(name, null);
10238 			}
10239 		});
10240 
10241 		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
10242 		htmlParser.addNodeFilter('script,style', function(nodes, name) {
10243 			var i = nodes.length, node, value;
10244 
10245 			function trim(value) {
10246 				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
10247 						.replace(/^[\r\n]*|[\r\n]*$/g, '')
10248 						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
10249 						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
10250 			};
10251 
10252 			while (i--) {
10253 				node = nodes[i];
10254 				value = node.firstChild ? node.firstChild.value : '';
10255 
10256 				if (name === "script") {
10257 					// Remove mce- prefix from script elements
10258 					node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
10259 
10260 					if (value.length > 0)
10261 						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
10262 				} else {
10263 					if (value.length > 0)
10264 						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
10265 				}
10266 			}
10267 		});
10268 
10269 		// Convert comments to cdata and handle protected comments
10270 		htmlParser.addNodeFilter('#comment', function(nodes, name) {
10271 			var i = nodes.length, node;
10272 
10273 			while (i--) {
10274 				node = nodes[i];
10275 
10276 				if (node.value.indexOf('[CDATA[') === 0) {
10277 					node.name = '#cdata';
10278 					node.type = 4;
10279 					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
10280 				} else if (node.value.indexOf('mce:protected ') === 0) {
10281 					node.name = "#text";
10282 					node.type = 3;
10283 					node.raw = true;
10284 					node.value = unescape(node.value).substr(14);
10285 				}
10286 			}
10287 		});
10288 
10289 		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
10290 			var i = nodes.length, node;
10291 
10292 			while (i--) {
10293 				node = nodes[i];
10294 				if (node.type === 7)
10295 					node.remove();
10296 				else if (node.type === 1) {
10297 					if (name === "input" && !("type" in node.attributes.map))
10298 						node.attr('type', 'text');
10299 				}
10300 			}
10301 		});
10302 
10303 		// Fix list elements, TODO: Replace this later
10304 		if (settings.fix_list_elements) {
10305 			htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
10306 				var i = nodes.length, node, parentNode;
10307 
10308 				while (i--) {
10309 					node = nodes[i];
10310 					parentNode = node.parent;
10311 
10312 					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
10313 						if (node.prev && node.prev.name === 'li') {
10314 							node.prev.append(node);
10315 						}
10316 					}
10317 				}
10318 			});
10319 		}
10320 
10321 		// Remove internal data attributes
10322 		htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
10323 			var i = nodes.length;
10324 
10325 			while (i--) {
10326 				nodes[i].attr(name, null);
10327 			}
10328 		});
10329 
10330 		// Return public methods
10331 		return {
10332 			schema : schema,
10333 
10334 			addNodeFilter : htmlParser.addNodeFilter,
10335 
10336 			addAttributeFilter : htmlParser.addAttributeFilter,
10337 
10338 			onPreProcess : onPreProcess,
10339 
10340 			onPostProcess : onPostProcess,
10341 
10342 			serialize : function(node, args) {
10343 				var impl, doc, oldDoc, htmlSerializer, content;
10344 
10345 				// Explorer won't clone contents of script and style and the
10346 				// selected index of select elements are cleared on a clone operation.
10347 				if (isIE && dom.select('script,style,select,map').length > 0) {
10348 					content = node.innerHTML;
10349 					node = node.cloneNode(false);
10350 					dom.setHTML(node, content);
10351 				} else
10352 					node = node.cloneNode(true);
10353 
10354 				// Nodes needs to be attached to something in WebKit/Opera
10355 				// Older builds of Opera crashes if you attach the node to an document created dynamically
10356 				// and since we can't feature detect a crash we need to sniff the acutal build number
10357 				// This fix will make DOM ranges and make Sizzle happy!
10358 				impl = node.ownerDocument.implementation;
10359 				if (impl.createHTMLDocument) {
10360 					// Create an empty HTML document
10361 					doc = impl.createHTMLDocument("");
10362 
10363 					// Add the element or it's children if it's a body element to the new document
10364 					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
10365 						doc.body.appendChild(doc.importNode(node, true));
10366 					});
10367 
10368 					// Grab first child or body element for serialization
10369 					if (node.nodeName != 'BODY')
10370 						node = doc.body.firstChild;
10371 					else
10372 						node = doc.body;
10373 
10374 					// set the new document in DOMUtils so createElement etc works
10375 					oldDoc = dom.doc;
10376 					dom.doc = doc;
10377 				}
10378 
10379 				args = args || {};
10380 				args.format = args.format || 'html';
10381 
10382 				// Pre process
10383 				if (!args.no_events) {
10384 					args.node = node;
10385 					onPreProcess.dispatch(self, args);
10386 				}
10387 
10388 				// Setup serializer
10389 				htmlSerializer = new tinymce.html.Serializer(settings, schema);
10390 
10391 				// Parse and serialize HTML
10392 				args.content = htmlSerializer.serialize(
10393 					htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
10394 				);
10395 
10396 				// Replace all BOM characters for now until we can find a better solution
10397 				if (!args.cleanup)
10398 					args.content = args.content.replace(/\uFEFF|\u200B/g, '');
10399 
10400 				// Post process
10401 				if (!args.no_events)
10402 					onPostProcess.dispatch(self, args);
10403 
10404 				// Restore the old document if it was changed
10405 				if (oldDoc)
10406 					dom.doc = oldDoc;
10407 
10408 				args.node = null;
10409 
10410 				return args.content;
10411 			},
10412 
10413 			addRules : function(rules) {
10414 				schema.addValidElements(rules);
10415 			},
10416 
10417 			setRules : function(rules) {
10418 				schema.setValidElements(rules);
10419 			}
10420 		};
10421 	};
10422 })(tinymce);
10423 (function(tinymce) {
10424 	tinymce.dom.ScriptLoader = function(settings) {
10425 		var QUEUED = 0,
10426 			LOADING = 1,
10427 			LOADED = 2,
10428 			states = {},
10429 			queue = [],
10430 			scriptLoadedCallbacks = {},
10431 			queueLoadedCallbacks = [],
10432 			loading = 0,
10433 			undef;
10434 
10435 		function loadScript(url, callback) {
10436 			var t = this, dom = tinymce.DOM, elm, uri, loc, id;
10437 
10438 			// Execute callback when script is loaded
10439 			function done() {
10440 				dom.remove(id);
10441 
10442 				if (elm)
10443 					elm.onreadystatechange = elm.onload = elm = null;
10444 
10445 				callback();
10446 			};
10447 			
10448 			function error() {
10449 				// Report the error so it's easier for people to spot loading errors
10450 				if (typeof(console) !== "undefined" && console.log)
10451 					console.log("Failed to load: " + url);
10452 
10453 				// We can't mark it as done if there is a load error since
10454 				// A) We don't want to produce 404 errors on the server and
10455 				// B) the onerror event won't fire on all browsers.
10456 				// done();
10457 			};
10458 
10459 			id = dom.uniqueId();
10460 
10461 			if (tinymce.isIE6) {
10462 				uri = new tinymce.util.URI(url);
10463 				loc = location;
10464 
10465 				// If script is from same domain and we
10466 				// use IE 6 then use XHR since it's more reliable
10467 				if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
10468 					tinymce.util.XHR.send({
10469 						url : tinymce._addVer(uri.getURI()),
10470 						success : function(content) {
10471 							// Create new temp script element
10472 							var script = dom.create('script', {
10473 								type : 'text/javascript'
10474 							});
10475 
10476 							// Evaluate script in global scope
10477 							script.text = content;
10478 							document.getElementsByTagName('head')[0].appendChild(script);
10479 							dom.remove(script);
10480 
10481 							done();
10482 						},
10483 						
10484 						error : error
10485 					});
10486 
10487 					return;
10488 				}
10489 			}
10490 
10491 			// Create new script element
10492 			elm = document.createElement('script');
10493 			elm.id = id;
10494 			elm.type = 'text/javascript';
10495 			elm.src = tinymce._addVer(url);
10496 
10497 			// Add onload listener for non IE browsers since IE9
10498 			// fires onload event before the script is parsed and executed
10499 			if (!tinymce.isIE)
10500 				elm.onload = done;
10501 
10502 			// Add onerror event will get fired on some browsers but not all of them
10503 			elm.onerror = error;
10504 
10505 			// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
10506 			if (!tinymce.isOpera) {
10507 				elm.onreadystatechange = function() {
10508 					var state = elm.readyState;
10509 
10510 					// Loaded state is passed on IE 6 however there
10511 					// are known issues with this method but we can't use
10512 					// XHR in a cross domain loading
10513 					if (state == 'complete' || state == 'loaded')
10514 						done();
10515 				};
10516 			}
10517 
10518 			// Most browsers support this feature so we report errors
10519 			// for those at least to help users track their missing plugins etc
10520 			// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
10521 			/*elm.onerror = function() {
10522 				alert('Failed to load: ' + url);
10523 			};*/
10524 
10525 			// Add script to document
10526 			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
10527 		};
10528 
10529 		this.isDone = function(url) {
10530 			return states[url] == LOADED;
10531 		};
10532 
10533 		this.markDone = function(url) {
10534 			states[url] = LOADED;
10535 		};
10536 
10537 		this.add = this.load = function(url, callback, scope) {
10538 			var item, state = states[url];
10539 
10540 			// Add url to load queue
10541 			if (state == undef) {
10542 				queue.push(url);
10543 				states[url] = QUEUED;
10544 			}
10545 
10546 			if (callback) {
10547 				// Store away callback for later execution
10548 				if (!scriptLoadedCallbacks[url])
10549 					scriptLoadedCallbacks[url] = [];
10550 
10551 				scriptLoadedCallbacks[url].push({
10552 					func : callback,
10553 					scope : scope || this
10554 				});
10555 			}
10556 		};
10557 
10558 		this.loadQueue = function(callback, scope) {
10559 			this.loadScripts(queue, callback, scope);
10560 		};
10561 
10562 		this.loadScripts = function(scripts, callback, scope) {
10563 			var loadScripts;
10564 
10565 			function execScriptLoadedCallbacks(url) {
10566 				// Execute URL callback functions
10567 				tinymce.each(scriptLoadedCallbacks[url], function(callback) {
10568 					callback.func.call(callback.scope);
10569 				});
10570 
10571 				scriptLoadedCallbacks[url] = undef;
10572 			};
10573 
10574 			queueLoadedCallbacks.push({
10575 				func : callback,
10576 				scope : scope || this
10577 			});
10578 
10579 			loadScripts = function() {
10580 				var loadingScripts = tinymce.grep(scripts);
10581 
10582 				// Current scripts has been handled
10583 				scripts.length = 0;
10584 
10585 				// Load scripts that needs to be loaded
10586 				tinymce.each(loadingScripts, function(url) {
10587 					// Script is already loaded then execute script callbacks directly
10588 					if (states[url] == LOADED) {
10589 						execScriptLoadedCallbacks(url);
10590 						return;
10591 					}
10592 
10593 					// Is script not loading then start loading it
10594 					if (states[url] != LOADING) {
10595 						states[url] = LOADING;
10596 						loading++;
10597 
10598 						loadScript(url, function() {
10599 							states[url] = LOADED;
10600 							loading--;
10601 
10602 							execScriptLoadedCallbacks(url);
10603 
10604 							// Load more scripts if they where added by the recently loaded script
10605 							loadScripts();
10606 						});
10607 					}
10608 				});
10609 
10610 				// No scripts are currently loading then execute all pending queue loaded callbacks
10611 				if (!loading) {
10612 					tinymce.each(queueLoadedCallbacks, function(callback) {
10613 						callback.func.call(callback.scope);
10614 					});
10615 
10616 					queueLoadedCallbacks.length = 0;
10617 				}
10618 			};
10619 
10620 			loadScripts();
10621 		};
10622 	};
10623 
10624 	// Global script loader
10625 	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
10626 })(tinymce);
10627 
10628 (function(tinymce) {
10629 	tinymce.dom.RangeUtils = function(dom) {
10630 		var INVISIBLE_CHAR = '\uFEFF';
10631 
10632 		this.walk = function(rng, callback) {
10633 			var startContainer = rng.startContainer,
10634 				startOffset = rng.startOffset,
10635 				endContainer = rng.endContainer,
10636 				endOffset = rng.endOffset,
10637 				ancestor, startPoint,
10638 				endPoint, node, parent, siblings, nodes;
10639 
10640 			// Handle table cell selection the table plugin enables
10641 			// you to fake select table cells and perform formatting actions on them
10642 			nodes = dom.select('td.mceSelected,th.mceSelected');
10643 			if (nodes.length > 0) {
10644 				tinymce.each(nodes, function(node) {
10645 					callback([node]);
10646 				});
10647 
10648 				return;
10649 			}
10650 
10651 			function exclude(nodes) {
10652 				var node;
10653 
10654 				// First node is excluded
10655 				node = nodes[0];
10656 				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
10657 					nodes.splice(0, 1);
10658 				}
10659 
10660 				// Last node is excluded
10661 				node = nodes[nodes.length - 1];
10662 				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
10663 					nodes.splice(nodes.length - 1, 1);
10664 				}
10665 
10666 				return nodes;
10667 			};
10668 
10669 			function collectSiblings(node, name, end_node) {
10670 				var siblings = [];
10671 
10672 				for (; node && node != end_node; node = node[name])
10673 					siblings.push(node);
10674 
10675 				return siblings;
10676 			};
10677 
10678 			function findEndPoint(node, root) {
10679 				do {
10680 					if (node.parentNode == root)
10681 						return node;
10682 
10683 					node = node.parentNode;
10684 				} while(node);
10685 			};
10686 
10687 			function walkBoundary(start_node, end_node, next) {
10688 				var siblingName = next ? 'nextSibling' : 'previousSibling';
10689 
10690 				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
10691 					parent = node.parentNode;
10692 					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
10693 
10694 					if (siblings.length) {
10695 						if (!next)
10696 							siblings.reverse();
10697 
10698 						callback(exclude(siblings));
10699 					}
10700 				}
10701 			};
10702 
10703 			// If index based start position then resolve it
10704 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
10705 				startContainer = startContainer.childNodes[startOffset];
10706 
10707 			// If index based end position then resolve it
10708 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
10709 				endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
10710 
10711 			// Same container
10712 			if (startContainer == endContainer)
10713 				return callback(exclude([startContainer]));
10714 
10715 			// Find common ancestor and end points
10716 			ancestor = dom.findCommonAncestor(startContainer, endContainer);
10717 				
10718 			// Process left side
10719 			for (node = startContainer; node; node = node.parentNode) {
10720 				if (node === endContainer)
10721 					return walkBoundary(startContainer, ancestor, true);
10722 
10723 				if (node === ancestor)
10724 					break;
10725 			}
10726 
10727 			// Process right side
10728 			for (node = endContainer; node; node = node.parentNode) {
10729 				if (node === startContainer)
10730 					return walkBoundary(endContainer, ancestor);
10731 
10732 				if (node === ancestor)
10733 					break;
10734 			}
10735 
10736 			// Find start/end point
10737 			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
10738 			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
10739 
10740 			// Walk left leaf
10741 			walkBoundary(startContainer, startPoint, true);
10742 
10743 			// Walk the middle from start to end point
10744 			siblings = collectSiblings(
10745 				startPoint == startContainer ? startPoint : startPoint.nextSibling,
10746 				'nextSibling',
10747 				endPoint == endContainer ? endPoint.nextSibling : endPoint
10748 			);
10749 
10750 			if (siblings.length)
10751 				callback(exclude(siblings));
10752 
10753 			// Walk right leaf
10754 			walkBoundary(endContainer, endPoint);
10755 		};
10756 
10757 		this.split = function(rng) {
10758 			var startContainer = rng.startContainer,
10759 				startOffset = rng.startOffset,
10760 				endContainer = rng.endContainer,
10761 				endOffset = rng.endOffset;
10762 
10763 			function splitText(node, offset) {
10764 				return node.splitText(offset);
10765 			};
10766 
10767 			// Handle single text node
10768 			if (startContainer == endContainer && startContainer.nodeType == 3) {
10769 				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10770 					endContainer = splitText(startContainer, startOffset);
10771 					startContainer = endContainer.previousSibling;
10772 
10773 					if (endOffset > startOffset) {
10774 						endOffset = endOffset - startOffset;
10775 						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
10776 						endOffset = endContainer.nodeValue.length;
10777 						startOffset = 0;
10778 					} else {
10779 						endOffset = 0;
10780 					}
10781 				}
10782 			} else {
10783 				// Split startContainer text node if needed
10784 				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10785 					startContainer = splitText(startContainer, startOffset);
10786 					startOffset = 0;
10787 				}
10788 
10789 				// Split endContainer text node if needed
10790 				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
10791 					endContainer = splitText(endContainer, endOffset).previousSibling;
10792 					endOffset = endContainer.nodeValue.length;
10793 				}
10794 			}
10795 
10796 			return {
10797 				startContainer : startContainer,
10798 				startOffset : startOffset,
10799 				endContainer : endContainer,
10800 				endOffset : endOffset
10801 			};
10802 		};
10803 
10804 	};
10805 
10806 	tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
10807 		if (rng1 && rng2) {
10808 			// Compare native IE ranges
10809 			if (rng1.item || rng1.duplicate) {
10810 				// Both are control ranges and the selected element matches
10811 				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
10812 					return true;
10813 
10814 				// Both are text ranges and the range matches
10815 				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
10816 					return true;
10817 			} else {
10818 				// Compare w3c ranges
10819 				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
10820 			}
10821 		}
10822 
10823 		return false;
10824 	};
10825 })(tinymce);
10826 
10827 (function(tinymce) {
10828 	var Event = tinymce.dom.Event, each = tinymce.each;
10829 
10830 	tinymce.create('tinymce.ui.KeyboardNavigation', {
10831 		KeyboardNavigation: function(settings, dom) {
10832 			var t = this, root = settings.root, items = settings.items,
10833 					enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
10834 					excludeFromTabOrder = settings.excludeFromTabOrder,
10835 					itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
10836 
10837 			dom = dom || tinymce.DOM;
10838 
10839 			itemFocussed = function(evt) {
10840 				focussedId = evt.target.id;
10841 			};
10842 			
10843 			itemBlurred = function(evt) {
10844 				dom.setAttrib(evt.target.id, 'tabindex', '-1');
10845 			};
10846 			
10847 			rootFocussed = function(evt) {
10848 				var item = dom.get(focussedId);
10849 				dom.setAttrib(item, 'tabindex', '0');
10850 				item.focus();
10851 			};
10852 			
10853 			t.focus = function() {
10854 				dom.get(focussedId).focus();
10855 			};
10856 
10857 			t.destroy = function() {
10858 				each(items, function(item) {
10859 					var elm = dom.get(item.id);
10860 
10861 					dom.unbind(elm, 'focus', itemFocussed);
10862 					dom.unbind(elm, 'blur', itemBlurred);
10863 				});
10864 
10865 				var rootElm = dom.get(root);
10866 				dom.unbind(rootElm, 'focus', rootFocussed);
10867 				dom.unbind(rootElm, 'keydown', rootKeydown);
10868 
10869 				items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
10870 				t.destroy = function() {};
10871 			};
10872 			
10873 			t.moveFocus = function(dir, evt) {
10874 				var idx = -1, controls = t.controls, newFocus;
10875 
10876 				if (!focussedId)
10877 					return;
10878 
10879 				each(items, function(item, index) {
10880 					if (item.id === focussedId) {
10881 						idx = index;
10882 						return false;
10883 					}
10884 				});
10885 
10886 				idx += dir;
10887 				if (idx < 0) {
10888 					idx = items.length - 1;
10889 				} else if (idx >= items.length) {
10890 					idx = 0;
10891 				}
10892 				
10893 				newFocus = items[idx];
10894 				dom.setAttrib(focussedId, 'tabindex', '-1');
10895 				dom.setAttrib(newFocus.id, 'tabindex', '0');
10896 				dom.get(newFocus.id).focus();
10897 
10898 				if (settings.actOnFocus) {
10899 					settings.onAction(newFocus.id);
10900 				}
10901 
10902 				if (evt)
10903 					Event.cancel(evt);
10904 			};
10905 			
10906 			rootKeydown = function(evt) {
10907 				var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
10908 				
10909 				switch (evt.keyCode) {
10910 					case DOM_VK_LEFT:
10911 						if (enableLeftRight) t.moveFocus(-1);
10912 						break;
10913 	
10914 					case DOM_VK_RIGHT:
10915 						if (enableLeftRight) t.moveFocus(1);
10916 						break;
10917 	
10918 					case DOM_VK_UP:
10919 						if (enableUpDown) t.moveFocus(-1);
10920 						break;
10921 
10922 					case DOM_VK_DOWN:
10923 						if (enableUpDown) t.moveFocus(1);
10924 						break;
10925 
10926 					case DOM_VK_ESCAPE:
10927 						if (settings.onCancel) {
10928 							settings.onCancel();
10929 							Event.cancel(evt);
10930 						}
10931 						break;
10932 
10933 					case DOM_VK_ENTER:
10934 					case DOM_VK_RETURN:
10935 					case DOM_VK_SPACE:
10936 						if (settings.onAction) {
10937 							settings.onAction(focussedId);
10938 							Event.cancel(evt);
10939 						}
10940 						break;
10941 				}
10942 			};
10943 
10944 			// Set up state and listeners for each item.
10945 			each(items, function(item, idx) {
10946 				var tabindex, elm;
10947 
10948 				if (!item.id) {
10949 					item.id = dom.uniqueId('_mce_item_');
10950 				}
10951 
10952 				elm = dom.get(item.id);
10953 
10954 				if (excludeFromTabOrder) {
10955 					dom.bind(elm, 'blur', itemBlurred);
10956 					tabindex = '-1';
10957 				} else {
10958 					tabindex = (idx === 0 ? '0' : '-1');
10959 				}
10960 
10961 				elm.setAttribute('tabindex', tabindex);
10962 				dom.bind(elm, 'focus', itemFocussed);
10963 			});
10964 			
10965 			// Setup initial state for root element.
10966 			if (items[0]){
10967 				focussedId = items[0].id;
10968 			}
10969 
10970 			dom.setAttrib(root, 'tabindex', '-1');
10971 
10972 			// Setup listeners for root element.
10973 			var rootElm = dom.get(root);
10974 			dom.bind(rootElm, 'focus', rootFocussed);
10975 			dom.bind(rootElm, 'keydown', rootKeydown);
10976 		}
10977 	});
10978 })(tinymce);
10979 
10980 (function(tinymce) {
10981 	// Shorten class names
10982 	var DOM = tinymce.DOM, is = tinymce.is;
10983 
10984 	tinymce.create('tinymce.ui.Control', {
10985 		Control : function(id, s, editor) {
10986 			this.id = id;
10987 			this.settings = s = s || {};
10988 			this.rendered = false;
10989 			this.onRender = new tinymce.util.Dispatcher(this);
10990 			this.classPrefix = '';
10991 			this.scope = s.scope || this;
10992 			this.disabled = 0;
10993 			this.active = 0;
10994 			this.editor = editor;
10995 		},
10996 		
10997 		setAriaProperty : function(property, value) {
10998 			var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
10999 			if (element) {
11000 				DOM.setAttrib(element, 'aria-' + property, !!value);
11001 			}
11002 		},
11003 		
11004 		focus : function() {
11005 			DOM.get(this.id).focus();
11006 		},
11007 
11008 		setDisabled : function(s) {
11009 			if (s != this.disabled) {
11010 				this.setAriaProperty('disabled', s);
11011 
11012 				this.setState('Disabled', s);
11013 				this.setState('Enabled', !s);
11014 				this.disabled = s;
11015 			}
11016 		},
11017 
11018 		isDisabled : function() {
11019 			return this.disabled;
11020 		},
11021 
11022 		setActive : function(s) {
11023 			if (s != this.active) {
11024 				this.setState('Active', s);
11025 				this.active = s;
11026 				this.setAriaProperty('pressed', s);
11027 			}
11028 		},
11029 
11030 		isActive : function() {
11031 			return this.active;
11032 		},
11033 
11034 		setState : function(c, s) {
11035 			var n = DOM.get(this.id);
11036 
11037 			c = this.classPrefix + c;
11038 
11039 			if (s)
11040 				DOM.addClass(n, c);
11041 			else
11042 				DOM.removeClass(n, c);
11043 		},
11044 
11045 		isRendered : function() {
11046 			return this.rendered;
11047 		},
11048 
11049 		renderHTML : function() {
11050 		},
11051 
11052 		renderTo : function(n) {
11053 			DOM.setHTML(n, this.renderHTML());
11054 		},
11055 
11056 		postRender : function() {
11057 			var t = this, b;
11058 
11059 			// Set pending states
11060 			if (is(t.disabled)) {
11061 				b = t.disabled;
11062 				t.disabled = -1;
11063 				t.setDisabled(b);
11064 			}
11065 
11066 			if (is(t.active)) {
11067 				b = t.active;
11068 				t.active = -1;
11069 				t.setActive(b);
11070 			}
11071 		},
11072 
11073 		remove : function() {
11074 			DOM.remove(this.id);
11075 			this.destroy();
11076 		},
11077 
11078 		destroy : function() {
11079 			tinymce.dom.Event.clear(this.id);
11080 		}
11081 	});
11082 })(tinymce);
11083 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
11084 	Container : function(id, s, editor) {
11085 		this.parent(id, s, editor);
11086 
11087 		this.controls = [];
11088 
11089 		this.lookup = {};
11090 	},
11091 
11092 	add : function(c) {
11093 		this.lookup[c.id] = c;
11094 		this.controls.push(c);
11095 
11096 		return c;
11097 	},
11098 
11099 	get : function(n) {
11100 		return this.lookup[n];
11101 	}
11102 });
11103 
11104 
11105 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
11106 	Separator : function(id, s) {
11107 		this.parent(id, s);
11108 		this.classPrefix = 'mceSeparator';
11109 		this.setDisabled(true);
11110 	},
11111 
11112 	renderHTML : function() {
11113 		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
11114 	}
11115 });
11116 
11117 (function(tinymce) {
11118 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11119 
11120 	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
11121 		MenuItem : function(id, s) {
11122 			this.parent(id, s);
11123 			this.classPrefix = 'mceMenuItem';
11124 		},
11125 
11126 		setSelected : function(s) {
11127 			this.setState('Selected', s);
11128 			this.setAriaProperty('checked', !!s);
11129 			this.selected = s;
11130 		},
11131 
11132 		isSelected : function() {
11133 			return this.selected;
11134 		},
11135 
11136 		postRender : function() {
11137 			var t = this;
11138 			
11139 			t.parent();
11140 
11141 			// Set pending state
11142 			if (is(t.selected))
11143 				t.setSelected(t.selected);
11144 		}
11145 	});
11146 })(tinymce);
11147 
11148 (function(tinymce) {
11149 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11150 
11151 	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
11152 		Menu : function(id, s) {
11153 			var t = this;
11154 
11155 			t.parent(id, s);
11156 			t.items = {};
11157 			t.collapsed = false;
11158 			t.menuCount = 0;
11159 			t.onAddItem = new tinymce.util.Dispatcher(this);
11160 		},
11161 
11162 		expand : function(d) {
11163 			var t = this;
11164 
11165 			if (d) {
11166 				walk(t, function(o) {
11167 					if (o.expand)
11168 						o.expand();
11169 				}, 'items', t);
11170 			}
11171 
11172 			t.collapsed = false;
11173 		},
11174 
11175 		collapse : function(d) {
11176 			var t = this;
11177 
11178 			if (d) {
11179 				walk(t, function(o) {
11180 					if (o.collapse)
11181 						o.collapse();
11182 				}, 'items', t);
11183 			}
11184 
11185 			t.collapsed = true;
11186 		},
11187 
11188 		isCollapsed : function() {
11189 			return this.collapsed;
11190 		},
11191 
11192 		add : function(o) {
11193 			if (!o.settings)
11194 				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
11195 
11196 			this.onAddItem.dispatch(this, o);
11197 
11198 			return this.items[o.id] = o;
11199 		},
11200 
11201 		addSeparator : function() {
11202 			return this.add({separator : true});
11203 		},
11204 
11205 		addMenu : function(o) {
11206 			if (!o.collapse)
11207 				o = this.createMenu(o);
11208 
11209 			this.menuCount++;
11210 
11211 			return this.add(o);
11212 		},
11213 
11214 		hasMenus : function() {
11215 			return this.menuCount !== 0;
11216 		},
11217 
11218 		remove : function(o) {
11219 			delete this.items[o.id];
11220 		},
11221 
11222 		removeAll : function() {
11223 			var t = this;
11224 
11225 			walk(t, function(o) {
11226 				if (o.removeAll)
11227 					o.removeAll();
11228 				else
11229 					o.remove();
11230 
11231 				o.destroy();
11232 			}, 'items', t);
11233 
11234 			t.items = {};
11235 		},
11236 
11237 		createMenu : function(o) {
11238 			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
11239 
11240 			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
11241 
11242 			return m;
11243 		}
11244 	});
11245 })(tinymce);
11246 (function(tinymce) {
11247 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
11248 
11249 	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
11250 		DropMenu : function(id, s) {
11251 			s = s || {};
11252 			s.container = s.container || DOM.doc.body;
11253 			s.offset_x = s.offset_x || 0;
11254 			s.offset_y = s.offset_y || 0;
11255 			s.vp_offset_x = s.vp_offset_x || 0;
11256 			s.vp_offset_y = s.vp_offset_y || 0;
11257 
11258 			if (is(s.icons) && !s.icons)
11259 				s['class'] += ' mceNoIcons';
11260 
11261 			this.parent(id, s);
11262 			this.onShowMenu = new tinymce.util.Dispatcher(this);
11263 			this.onHideMenu = new tinymce.util.Dispatcher(this);
11264 			this.classPrefix = 'mceMenu';
11265 		},
11266 
11267 		createMenu : function(s) {
11268 			var t = this, cs = t.settings, m;
11269 
11270 			s.container = s.container || cs.container;
11271 			s.parent = t;
11272 			s.constrain = s.constrain || cs.constrain;
11273 			s['class'] = s['class'] || cs['class'];
11274 			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
11275 			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
11276 			s.keyboard_focus = cs.keyboard_focus;
11277 			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
11278 
11279 			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
11280 
11281 			return m;
11282 		},
11283 		
11284 		focus : function() {
11285 			var t = this;
11286 			if (t.keyboardNav) {
11287 				t.keyboardNav.focus();
11288 			}
11289 		},
11290 
11291 		update : function() {
11292 			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
11293 
11294 			tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
11295 			th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
11296 
11297 			if (!DOM.boxModel)
11298 				t.element.setStyles({width : tw + 2, height : th + 2});
11299 			else
11300 				t.element.setStyles({width : tw, height : th});
11301 
11302 			if (s.max_width)
11303 				DOM.setStyle(co, 'width', tw);
11304 
11305 			if (s.max_height) {
11306 				DOM.setStyle(co, 'height', th);
11307 
11308 				if (tb.clientHeight < s.max_height)
11309 					DOM.setStyle(co, 'overflow', 'hidden');
11310 			}
11311 		},
11312 
11313 		showMenu : function(x, y, px) {
11314 			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
11315 
11316 			t.collapse(1);
11317 
11318 			if (t.isMenuVisible)
11319 				return;
11320 
11321 			if (!t.rendered) {
11322 				co = DOM.add(t.settings.container, t.renderNode());
11323 
11324 				each(t.items, function(o) {
11325 					o.postRender();
11326 				});
11327 
11328 				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11329 			} else
11330 				co = DOM.get('menu_' + t.id);
11331 
11332 			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
11333 			if (!tinymce.isOpera)
11334 				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
11335 
11336 			DOM.show(co);
11337 			t.update();
11338 
11339 			x += s.offset_x || 0;
11340 			y += s.offset_y || 0;
11341 			vp.w -= 4;
11342 			vp.h -= 4;
11343 
11344 			// Move inside viewport if not submenu
11345 			if (s.constrain) {
11346 				w = co.clientWidth - ot;
11347 				h = co.clientHeight - ot;
11348 				mx = vp.x + vp.w;
11349 				my = vp.y + vp.h;
11350 
11351 				if ((x + s.vp_offset_x + w) > mx)
11352 					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
11353 
11354 				if ((y + s.vp_offset_y + h) > my)
11355 					y = Math.max(0, (my - s.vp_offset_y) - h);
11356 			}
11357 
11358 			DOM.setStyles(co, {left : x , top : y});
11359 			t.element.update();
11360 
11361 			t.isMenuVisible = 1;
11362 			t.mouseClickFunc = Event.add(co, 'click', function(e) {
11363 				var m;
11364 
11365 				e = e.target;
11366 
11367 				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
11368 					m = t.items[e.id];
11369 
11370 					if (m.isDisabled())
11371 						return;
11372 
11373 					dm = t;
11374 
11375 					while (dm) {
11376 						if (dm.hideMenu)
11377 							dm.hideMenu();
11378 
11379 						dm = dm.settings.parent;
11380 					}
11381 
11382 					if (m.settings.onclick)
11383 						m.settings.onclick(e);
11384 
11385 					return false; // Cancel to fix onbeforeunload problem
11386 				}
11387 			});
11388 
11389 			if (t.hasMenus()) {
11390 				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
11391 					var m, r, mi;
11392 
11393 					e = e.target;
11394 					if (e && (e = DOM.getParent(e, 'tr'))) {
11395 						m = t.items[e.id];
11396 
11397 						if (t.lastMenu)
11398 							t.lastMenu.collapse(1);
11399 
11400 						if (m.isDisabled())
11401 							return;
11402 
11403 						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
11404 							//p = DOM.getPos(s.container);
11405 							r = DOM.getRect(e);
11406 							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
11407 							t.lastMenu = m;
11408 							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
11409 						}
11410 					}
11411 				});
11412 			}
11413 			
11414 			Event.add(co, 'keydown', t._keyHandler, t);
11415 
11416 			t.onShowMenu.dispatch(t);
11417 
11418 			if (s.keyboard_focus) { 
11419 				t._setupKeyboardNav(); 
11420 			}
11421 		},
11422 
11423 		hideMenu : function(c) {
11424 			var t = this, co = DOM.get('menu_' + t.id), e;
11425 
11426 			if (!t.isMenuVisible)
11427 				return;
11428 
11429 			if (t.keyboardNav) t.keyboardNav.destroy();
11430 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11431 			Event.remove(co, 'click', t.mouseClickFunc);
11432 			Event.remove(co, 'keydown', t._keyHandler);
11433 			DOM.hide(co);
11434 			t.isMenuVisible = 0;
11435 
11436 			if (!c)
11437 				t.collapse(1);
11438 
11439 			if (t.element)
11440 				t.element.hide();
11441 
11442 			if (e = DOM.get(t.id))
11443 				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
11444 
11445 			t.onHideMenu.dispatch(t);
11446 		},
11447 
11448 		add : function(o) {
11449 			var t = this, co;
11450 
11451 			o = t.parent(o);
11452 
11453 			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
11454 				t._add(DOM.select('tbody', co)[0], o);
11455 
11456 			return o;
11457 		},
11458 
11459 		collapse : function(d) {
11460 			this.parent(d);
11461 			this.hideMenu(1);
11462 		},
11463 
11464 		remove : function(o) {
11465 			DOM.remove(o.id);
11466 			this.destroy();
11467 
11468 			return this.parent(o);
11469 		},
11470 
11471 		destroy : function() {
11472 			var t = this, co = DOM.get('menu_' + t.id);
11473 
11474 			if (t.keyboardNav) t.keyboardNav.destroy();
11475 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11476 			Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
11477 			Event.remove(co, 'click', t.mouseClickFunc);
11478 			Event.remove(co, 'keydown', t._keyHandler);
11479 
11480 			if (t.element)
11481 				t.element.remove();
11482 
11483 			DOM.remove(co);
11484 		},
11485 
11486 		renderNode : function() {
11487 			var t = this, s = t.settings, n, tb, co, w;
11488 
11489 			w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
11490 			if (t.settings.parent) {
11491 				DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
11492 			}
11493 			co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
11494 			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11495 
11496 			if (s.menu_line)
11497 				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
11498 
11499 //			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
11500 			n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
11501 			tb = DOM.add(n, 'tbody');
11502 
11503 			each(t.items, function(o) {
11504 				t._add(tb, o);
11505 			});
11506 
11507 			t.rendered = true;
11508 
11509 			return w;
11510 		},
11511 
11512 		// Internal functions
11513 		_setupKeyboardNav : function(){
11514 			var contextMenu, menuItems, t=this; 
11515 			contextMenu = DOM.get('menu_' + t.id);
11516 			menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
11517 			menuItems.splice(0,0,contextMenu);
11518 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11519 				root: 'menu_' + t.id,
11520 				items: menuItems,
11521 				onCancel: function() {
11522 					t.hideMenu();
11523 				},
11524 				enableUpDown: true
11525 			});
11526 			contextMenu.focus();
11527 		},
11528 
11529 		_keyHandler : function(evt) {
11530 			var t = this, e;
11531 			switch (evt.keyCode) {
11532 				case 37: // Left
11533 					if (t.settings.parent) {
11534 						t.hideMenu();
11535 						t.settings.parent.focus();
11536 						Event.cancel(evt);
11537 					}
11538 					break;
11539 				case 39: // Right
11540 					if (t.mouseOverFunc)
11541 						t.mouseOverFunc(evt);
11542 					break;
11543 			}
11544 		},
11545 
11546 		_add : function(tb, o) {
11547 			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
11548 
11549 			if (s.separator) {
11550 				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
11551 				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
11552 
11553 				if (n = ro.previousSibling)
11554 					DOM.addClass(n, 'mceLast');
11555 
11556 				return;
11557 			}
11558 
11559 			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
11560 			n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
11561 			n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
11562 
11563 			if (s.parent) {
11564 				DOM.setAttrib(a, 'aria-haspopup', 'true');
11565 				DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
11566 			}
11567 
11568 			DOM.addClass(it, s['class']);
11569 //			n = DOM.add(n, 'span', {'class' : 'item'});
11570 
11571 			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
11572 
11573 			if (s.icon_src)
11574 				DOM.add(ic, 'img', {src : s.icon_src});
11575 
11576 			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
11577 
11578 			if (o.settings.style) {
11579 				if (typeof o.settings.style == "function")
11580 					o.settings.style = o.settings.style();
11581 
11582 				DOM.setAttrib(n, 'style', o.settings.style);
11583 			}
11584 
11585 			if (tb.childNodes.length == 1)
11586 				DOM.addClass(ro, 'mceFirst');
11587 
11588 			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
11589 				DOM.addClass(ro, 'mceFirst');
11590 
11591 			if (o.collapse)
11592 				DOM.addClass(ro, cp + 'ItemSub');
11593 
11594 			if (n = ro.previousSibling)
11595 				DOM.removeClass(n, 'mceLast');
11596 
11597 			DOM.addClass(ro, 'mceLast');
11598 		}
11599 	});
11600 })(tinymce);
11601 (function(tinymce) {
11602 	var DOM = tinymce.DOM;
11603 
11604 	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
11605 		Button : function(id, s, ed) {
11606 			this.parent(id, s, ed);
11607 			this.classPrefix = 'mceButton';
11608 		},
11609 
11610 		renderHTML : function() {
11611 			var cp = this.classPrefix, s = this.settings, h, l;
11612 
11613 			l = DOM.encode(s.label || '');
11614 			h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
11615 			if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
11616 				h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11617 			else
11618 				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11619 
11620 			h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
11621 			h += '</a>';
11622 			return h;
11623 		},
11624 
11625 		postRender : function() {
11626 			var t = this, s = t.settings, imgBookmark;
11627 
11628 			// In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
11629 			// need to keep the selection in case the selection is lost
11630 			if (tinymce.isIE && t.editor) {
11631 				tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
11632 					var nodeName = t.editor.selection.getNode().nodeName;
11633 					imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
11634 				});
11635 			}
11636 			tinymce.dom.Event.add(t.id, 'click', function(e) {
11637 				if (!t.isDisabled()) {
11638 					// restore the selection in case the selection is lost in IE
11639 					if (tinymce.isIE && t.editor && imgBookmark !== null) {
11640 						t.editor.selection.moveToBookmark(imgBookmark);
11641 					}
11642 					return s.onclick.call(s.scope, e);
11643 				}
11644 			});
11645 			tinymce.dom.Event.add(t.id, 'keyup', function(e) {
11646 				if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
11647 					return s.onclick.call(s.scope, e);
11648 			});
11649 		}
11650 	});
11651 })(tinymce);
11652 
11653 (function(tinymce) {
11654 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11655 
11656 	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
11657 		ListBox : function(id, s, ed) {
11658 			var t = this;
11659 
11660 			t.parent(id, s, ed);
11661 
11662 			t.items = [];
11663 
11664 			t.onChange = new Dispatcher(t);
11665 
11666 			t.onPostRender = new Dispatcher(t);
11667 
11668 			t.onAdd = new Dispatcher(t);
11669 
11670 			t.onRenderMenu = new tinymce.util.Dispatcher(this);
11671 
11672 			t.classPrefix = 'mceListBox';
11673 			t.marked = {};
11674 		},
11675 
11676 		select : function(va) {
11677 			var t = this, fv, f;
11678 
11679 			t.marked = {};
11680 
11681 			if (va == undef)
11682 				return t.selectByIndex(-1);
11683 
11684 			// Is string or number make function selector
11685 			if (va && typeof(va)=="function")
11686 				f = va;
11687 			else {
11688 				f = function(v) {
11689 					return v == va;
11690 				};
11691 			}
11692 
11693 			// Do we need to do something?
11694 			if (va != t.selectedValue) {
11695 				// Find item
11696 				each(t.items, function(o, i) {
11697 					if (f(o.value)) {
11698 						fv = 1;
11699 						t.selectByIndex(i);
11700 						return false;
11701 					}
11702 				});
11703 
11704 				if (!fv)
11705 					t.selectByIndex(-1);
11706 			}
11707 		},
11708 
11709 		selectByIndex : function(idx) {
11710 			var t = this, e, o, label;
11711 
11712 			t.marked = {};
11713 
11714 			if (idx != t.selectedIndex) {
11715 				e = DOM.get(t.id + '_text');
11716 				label = DOM.get(t.id + '_voiceDesc');
11717 				o = t.items[idx];
11718 
11719 				if (o) {
11720 					t.selectedValue = o.value;
11721 					t.selectedIndex = idx;
11722 					DOM.setHTML(e, DOM.encode(o.title));
11723 					DOM.setHTML(label, t.settings.title + " - " + o.title);
11724 					DOM.removeClass(e, 'mceTitle');
11725 					DOM.setAttrib(t.id, 'aria-valuenow', o.title);
11726 				} else {
11727 					DOM.setHTML(e, DOM.encode(t.settings.title));
11728 					DOM.setHTML(label, DOM.encode(t.settings.title));
11729 					DOM.addClass(e, 'mceTitle');
11730 					t.selectedValue = t.selectedIndex = null;
11731 					DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
11732 				}
11733 				e = 0;
11734 			}
11735 		},
11736 
11737 		mark : function(value) {
11738 			this.marked[value] = true;
11739 		},
11740 
11741 		add : function(n, v, o) {
11742 			var t = this;
11743 
11744 			o = o || {};
11745 			o = tinymce.extend(o, {
11746 				title : n,
11747 				value : v
11748 			});
11749 
11750 			t.items.push(o);
11751 			t.onAdd.dispatch(t, o);
11752 		},
11753 
11754 		getLength : function() {
11755 			return this.items.length;
11756 		},
11757 
11758 		renderHTML : function() {
11759 			var h = '', t = this, s = t.settings, cp = t.classPrefix;
11760 
11761 			h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
11762 			h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
11763 			h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
11764 			h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
11765 			h += '</tr></tbody></table></span>';
11766 
11767 			return h;
11768 		},
11769 
11770 		showMenu : function() {
11771 			var t = this, p2, e = DOM.get(this.id), m;
11772 
11773 			if (t.isDisabled() || t.items.length === 0)
11774 				return;
11775 
11776 			if (t.menu && t.menu.isMenuVisible)
11777 				return t.hideMenu();
11778 
11779 			if (!t.isMenuRendered) {
11780 				t.renderMenu();
11781 				t.isMenuRendered = true;
11782 			}
11783 
11784 			p2 = DOM.getPos(e);
11785 
11786 			m = t.menu;
11787 			m.settings.offset_x = p2.x;
11788 			m.settings.offset_y = p2.y;
11789 			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
11790 
11791 			// Select in menu
11792 			each(t.items, function(o) {
11793 				if (m.items[o.id]) {
11794 					m.items[o.id].setSelected(0);
11795 				}
11796 			});
11797 
11798 			each(t.items, function(o) {
11799 				if (m.items[o.id] && t.marked[o.value]) {
11800 					m.items[o.id].setSelected(1);
11801 				}
11802 
11803 				if (o.value === t.selectedValue) {
11804 					m.items[o.id].setSelected(1);
11805 				}
11806 			});
11807 
11808 			m.showMenu(0, e.clientHeight);
11809 
11810 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
11811 			DOM.addClass(t.id, t.classPrefix + 'Selected');
11812 
11813 			//DOM.get(t.id + '_text').focus();
11814 		},
11815 
11816 		hideMenu : function(e) {
11817 			var t = this;
11818 
11819 			if (t.menu && t.menu.isMenuVisible) {
11820 				DOM.removeClass(t.id, t.classPrefix + 'Selected');
11821 
11822 				// Prevent double toogles by canceling the mouse click event to the button
11823 				if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
11824 					return;
11825 
11826 				if (!e || !DOM.getParent(e.target, '.mceMenu')) {
11827 					DOM.removeClass(t.id, t.classPrefix + 'Selected');
11828 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
11829 					t.menu.hideMenu();
11830 				}
11831 			}
11832 		},
11833 
11834 		renderMenu : function() {
11835 			var t = this, m;
11836 
11837 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
11838 				menu_line : 1,
11839 				'class' : t.classPrefix + 'Menu mceNoIcons',
11840 				max_width : 250,
11841 				max_height : 150
11842 			});
11843 
11844 			m.onHideMenu.add(function() {
11845 				t.hideMenu();
11846 				t.focus();
11847 			});
11848 
11849 			m.add({
11850 				title : t.settings.title,
11851 				'class' : 'mceMenuItemTitle',
11852 				onclick : function() {
11853 					if (t.settings.onselect('') !== false)
11854 						t.select(''); // Must be runned after
11855 				}
11856 			});
11857 
11858 			each(t.items, function(o) {
11859 				// No value then treat it as a title
11860 				if (o.value === undef) {
11861 					m.add({
11862 						title : o.title,
11863 						role : "option",
11864 						'class' : 'mceMenuItemTitle',
11865 						onclick : function() {
11866 							if (t.settings.onselect('') !== false)
11867 								t.select(''); // Must be runned after
11868 						}
11869 					});
11870 				} else {
11871 					o.id = DOM.uniqueId();
11872 					o.role= "option";
11873 					o.onclick = function() {
11874 						if (t.settings.onselect(o.value) !== false)
11875 							t.select(o.value); // Must be runned after
11876 					};
11877 
11878 					m.add(o);
11879 				}
11880 			});
11881 
11882 			t.onRenderMenu.dispatch(t, m);
11883 			t.menu = m;
11884 		},
11885 
11886 		postRender : function() {
11887 			var t = this, cp = t.classPrefix;
11888 
11889 			Event.add(t.id, 'click', t.showMenu, t);
11890 			Event.add(t.id, 'keydown', function(evt) {
11891 				if (evt.keyCode == 32) { // Space
11892 					t.showMenu(evt);
11893 					Event.cancel(evt);
11894 				}
11895 			});
11896 			Event.add(t.id, 'focus', function() {
11897 				if (!t._focused) {
11898 					t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
11899 						if (e.keyCode == 40) {
11900 							t.showMenu();
11901 							Event.cancel(e);
11902 						}
11903 					});
11904 					t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
11905 						var v;
11906 						if (e.keyCode == 13) {
11907 							// Fake select on enter
11908 							v = t.selectedValue;
11909 							t.selectedValue = null; // Needs to be null to fake change
11910 							Event.cancel(e);
11911 							t.settings.onselect(v);
11912 						}
11913 					});
11914 				}
11915 
11916 				t._focused = 1;
11917 			});
11918 			Event.add(t.id, 'blur', function() {
11919 				Event.remove(t.id, 'keydown', t.keyDownHandler);
11920 				Event.remove(t.id, 'keypress', t.keyPressHandler);
11921 				t._focused = 0;
11922 			});
11923 
11924 			// Old IE doesn't have hover on all elements
11925 			if (tinymce.isIE6 || !DOM.boxModel) {
11926 				Event.add(t.id, 'mouseover', function() {
11927 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11928 						DOM.addClass(t.id, cp + 'Hover');
11929 				});
11930 
11931 				Event.add(t.id, 'mouseout', function() {
11932 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11933 						DOM.removeClass(t.id, cp + 'Hover');
11934 				});
11935 			}
11936 
11937 			t.onPostRender.dispatch(t, DOM.get(t.id));
11938 		},
11939 
11940 		destroy : function() {
11941 			this.parent();
11942 
11943 			Event.clear(this.id + '_text');
11944 			Event.clear(this.id + '_open');
11945 		}
11946 	});
11947 })(tinymce);
11948 
11949 (function(tinymce) {
11950 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11951 
11952 	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
11953 		NativeListBox : function(id, s) {
11954 			this.parent(id, s);
11955 			this.classPrefix = 'mceNativeListBox';
11956 		},
11957 
11958 		setDisabled : function(s) {
11959 			DOM.get(this.id).disabled = s;
11960 			this.setAriaProperty('disabled', s);
11961 		},
11962 
11963 		isDisabled : function() {
11964 			return DOM.get(this.id).disabled;
11965 		},
11966 
11967 		select : function(va) {
11968 			var t = this, fv, f;
11969 
11970 			if (va == undef)
11971 				return t.selectByIndex(-1);
11972 
11973 			// Is string or number make function selector
11974 			if (va && typeof(va)=="function")
11975 				f = va;
11976 			else {
11977 				f = function(v) {
11978 					return v == va;
11979 				};
11980 			}
11981 
11982 			// Do we need to do something?
11983 			if (va != t.selectedValue) {
11984 				// Find item
11985 				each(t.items, function(o, i) {
11986 					if (f(o.value)) {
11987 						fv = 1;
11988 						t.selectByIndex(i);
11989 						return false;
11990 					}
11991 				});
11992 
11993 				if (!fv)
11994 					t.selectByIndex(-1);
11995 			}
11996 		},
11997 
11998 		selectByIndex : function(idx) {
11999 			DOM.get(this.id).selectedIndex = idx + 1;
12000 			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
12001 		},
12002 
12003 		add : function(n, v, a) {
12004 			var o, t = this;
12005 
12006 			a = a || {};
12007 			a.value = v;
12008 
12009 			if (t.isRendered())
12010 				DOM.add(DOM.get(this.id), 'option', a, n);
12011 
12012 			o = {
12013 				title : n,
12014 				value : v,
12015 				attribs : a
12016 			};
12017 
12018 			t.items.push(o);
12019 			t.onAdd.dispatch(t, o);
12020 		},
12021 
12022 		getLength : function() {
12023 			return this.items.length;
12024 		},
12025 
12026 		renderHTML : function() {
12027 			var h, t = this;
12028 
12029 			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
12030 
12031 			each(t.items, function(it) {
12032 				h += DOM.createHTML('option', {value : it.value}, it.title);
12033 			});
12034 
12035 			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
12036 			h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
12037 			return h;
12038 		},
12039 
12040 		postRender : function() {
12041 			var t = this, ch, changeListenerAdded = true;
12042 
12043 			t.rendered = true;
12044 
12045 			function onChange(e) {
12046 				var v = t.items[e.target.selectedIndex - 1];
12047 
12048 				if (v && (v = v.value)) {
12049 					t.onChange.dispatch(t, v);
12050 
12051 					if (t.settings.onselect)
12052 						t.settings.onselect(v);
12053 				}
12054 			};
12055 
12056 			Event.add(t.id, 'change', onChange);
12057 
12058 			// Accessibility keyhandler
12059 			Event.add(t.id, 'keydown', function(e) {
12060 				var bf;
12061 
12062 				Event.remove(t.id, 'change', ch);
12063 				changeListenerAdded = false;
12064 
12065 				bf = Event.add(t.id, 'blur', function() {
12066 					if (changeListenerAdded) return;
12067 					changeListenerAdded = true;
12068 					Event.add(t.id, 'change', onChange);
12069 					Event.remove(t.id, 'blur', bf);
12070 				});
12071 
12072 				//prevent default left and right keys on chrome - so that the keyboard navigation is used.
12073 				if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
12074 					return Event.prevent(e);
12075 				}
12076 				
12077 				if (e.keyCode == 13 || e.keyCode == 32) {
12078 					onChange(e);
12079 					return Event.cancel(e);
12080 				}
12081 			});
12082 
12083 			t.onPostRender.dispatch(t, DOM.get(t.id));
12084 		}
12085 	});
12086 })(tinymce);
12087 
12088 (function(tinymce) {
12089 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12090 
12091 	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
12092 		MenuButton : function(id, s, ed) {
12093 			this.parent(id, s, ed);
12094 
12095 			this.onRenderMenu = new tinymce.util.Dispatcher(this);
12096 
12097 			s.menu_container = s.menu_container || DOM.doc.body;
12098 		},
12099 
12100 		showMenu : function() {
12101 			var t = this, p1, p2, e = DOM.get(t.id), m;
12102 
12103 			if (t.isDisabled())
12104 				return;
12105 
12106 			if (!t.isMenuRendered) {
12107 				t.renderMenu();
12108 				t.isMenuRendered = true;
12109 			}
12110 
12111 			if (t.isMenuVisible)
12112 				return t.hideMenu();
12113 
12114 			p1 = DOM.getPos(t.settings.menu_container);
12115 			p2 = DOM.getPos(e);
12116 
12117 			m = t.menu;
12118 			m.settings.offset_x = p2.x;
12119 			m.settings.offset_y = p2.y;
12120 			m.settings.vp_offset_x = p2.x;
12121 			m.settings.vp_offset_y = p2.y;
12122 			m.settings.keyboard_focus = t._focused;
12123 			m.showMenu(0, e.firstChild.clientHeight);
12124 
12125 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12126 			t.setState('Selected', 1);
12127 
12128 			t.isMenuVisible = 1;
12129 		},
12130 
12131 		renderMenu : function() {
12132 			var t = this, m;
12133 
12134 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12135 				menu_line : 1,
12136 				'class' : this.classPrefix + 'Menu',
12137 				icons : t.settings.icons
12138 			});
12139 
12140 			m.onHideMenu.add(function() {
12141 				t.hideMenu();
12142 				t.focus();
12143 			});
12144 
12145 			t.onRenderMenu.dispatch(t, m);
12146 			t.menu = m;
12147 		},
12148 
12149 		hideMenu : function(e) {
12150 			var t = this;
12151 
12152 			// Prevent double toogles by canceling the mouse click event to the button
12153 			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
12154 				return;
12155 
12156 			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12157 				t.setState('Selected', 0);
12158 				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12159 				if (t.menu)
12160 					t.menu.hideMenu();
12161 			}
12162 
12163 			t.isMenuVisible = 0;
12164 		},
12165 
12166 		postRender : function() {
12167 			var t = this, s = t.settings;
12168 
12169 			Event.add(t.id, 'click', function() {
12170 				if (!t.isDisabled()) {
12171 					if (s.onclick)
12172 						s.onclick(t.value);
12173 
12174 					t.showMenu();
12175 				}
12176 			});
12177 		}
12178 	});
12179 })(tinymce);
12180 
12181 (function(tinymce) {
12182 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12183 
12184 	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
12185 		SplitButton : function(id, s, ed) {
12186 			this.parent(id, s, ed);
12187 			this.classPrefix = 'mceSplitButton';
12188 		},
12189 
12190 		renderHTML : function() {
12191 			var h, t = this, s = t.settings, h1;
12192 
12193 			h = '<tbody><tr>';
12194 
12195 			if (s.image)
12196 				h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
12197 			else
12198 				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
12199 
12200 			h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
12201 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12202 	
12203 			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
12204 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12205 
12206 			h += '</tr></tbody>';
12207 			h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
12208 			return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
12209 		},
12210 
12211 		postRender : function() {
12212 			var t = this, s = t.settings, activate;
12213 
12214 			if (s.onclick) {
12215 				activate = function(evt) {
12216 					if (!t.isDisabled()) {
12217 						s.onclick(t.value);
12218 						Event.cancel(evt);
12219 					}
12220 				};
12221 				Event.add(t.id + '_action', 'click', activate);
12222 				Event.add(t.id, ['click', 'keydown'], function(evt) {
12223 					var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
12224 					if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
12225 						activate();
12226 						Event.cancel(evt);
12227 					} else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
12228 						t.showMenu();
12229 						Event.cancel(evt);
12230 					}
12231 				});
12232 			}
12233 
12234 			Event.add(t.id + '_open', 'click', function (evt) {
12235 				t.showMenu();
12236 				Event.cancel(evt);
12237 			});
12238 			Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
12239 			Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
12240 
12241 			// Old IE doesn't have hover on all elements
12242 			if (tinymce.isIE6 || !DOM.boxModel) {
12243 				Event.add(t.id, 'mouseover', function() {
12244 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12245 						DOM.addClass(t.id, 'mceSplitButtonHover');
12246 				});
12247 
12248 				Event.add(t.id, 'mouseout', function() {
12249 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12250 						DOM.removeClass(t.id, 'mceSplitButtonHover');
12251 				});
12252 			}
12253 		},
12254 
12255 		destroy : function() {
12256 			this.parent();
12257 
12258 			Event.clear(this.id + '_action');
12259 			Event.clear(this.id + '_open');
12260 			Event.clear(this.id);
12261 		}
12262 	});
12263 })(tinymce);
12264 
12265 (function(tinymce) {
12266 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
12267 
12268 	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
12269 		ColorSplitButton : function(id, s, ed) {
12270 			var t = this;
12271 
12272 			t.parent(id, s, ed);
12273 
12274 			t.settings = s = tinymce.extend({
12275 				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
12276 				grid_width : 8,
12277 				default_color : '#888888'
12278 			}, t.settings);
12279 
12280 			t.onShowMenu = new tinymce.util.Dispatcher(t);
12281 
12282 			t.onHideMenu = new tinymce.util.Dispatcher(t);
12283 
12284 			t.value = s.default_color;
12285 		},
12286 
12287 		showMenu : function() {
12288 			var t = this, r, p, e, p2;
12289 
12290 			if (t.isDisabled())
12291 				return;
12292 
12293 			if (!t.isMenuRendered) {
12294 				t.renderMenu();
12295 				t.isMenuRendered = true;
12296 			}
12297 
12298 			if (t.isMenuVisible)
12299 				return t.hideMenu();
12300 
12301 			e = DOM.get(t.id);
12302 			DOM.show(t.id + '_menu');
12303 			DOM.addClass(e, 'mceSplitButtonSelected');
12304 			p2 = DOM.getPos(e);
12305 			DOM.setStyles(t.id + '_menu', {
12306 				left : p2.x,
12307 				top : p2.y + e.firstChild.clientHeight,
12308 				zIndex : 200000
12309 			});
12310 			e = 0;
12311 
12312 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12313 			t.onShowMenu.dispatch(t);
12314 
12315 			if (t._focused) {
12316 				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
12317 					if (e.keyCode == 27)
12318 						t.hideMenu();
12319 				});
12320 
12321 				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
12322 			}
12323 
12324 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
12325 				root: t.id + '_menu',
12326 				items: DOM.select('a', t.id + '_menu'),
12327 				onCancel: function() {
12328 					t.hideMenu();
12329 					t.focus();
12330 				}
12331 			});
12332 
12333 			t.keyboardNav.focus();
12334 			t.isMenuVisible = 1;
12335 		},
12336 
12337 		hideMenu : function(e) {
12338 			var t = this;
12339 
12340 			if (t.isMenuVisible) {
12341 				// Prevent double toogles by canceling the mouse click event to the button
12342 				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
12343 					return;
12344 
12345 				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
12346 					DOM.removeClass(t.id, 'mceSplitButtonSelected');
12347 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12348 					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
12349 					DOM.hide(t.id + '_menu');
12350 				}
12351 
12352 				t.isMenuVisible = 0;
12353 				t.onHideMenu.dispatch();
12354 				t.keyboardNav.destroy();
12355 			}
12356 		},
12357 
12358 		renderMenu : function() {
12359 			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
12360 
12361 			w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
12362 			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
12363 			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
12364 
12365 			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
12366 			tb = DOM.add(n, 'tbody');
12367 
12368 			// Generate color grid
12369 			i = 0;
12370 			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
12371 				c = c.replace(/^#/, '');
12372 
12373 				if (!i--) {
12374 					tr = DOM.add(tb, 'tr');
12375 					i = s.grid_width - 1;
12376 				}
12377 
12378 				n = DOM.add(tr, 'td');
12379 				var settings = {
12380 					href : 'javascript:;',
12381 					style : {
12382 						backgroundColor : '#' + c
12383 					},
12384 					'title': t.editor.getLang('colors.' + c, c),
12385 					'data-mce-color' : '#' + c
12386 				};
12387 
12388 				// adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
12389 				if (!tinymce.isIE ) {
12390 					settings.role = 'option';
12391 				}
12392 
12393 				n = DOM.add(n, 'a', settings);
12394 
12395 				if (t.editor.forcedHighContrastMode) {
12396 					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
12397 					if (n.getContext && (context = n.getContext("2d"))) {
12398 						context.fillStyle = '#' + c;
12399 						context.fillRect(0, 0, 16, 16);
12400 					} else {
12401 						// No point leaving a canvas element around if it's not supported for drawing on anyway.
12402 						DOM.remove(n);
12403 					}
12404 				}
12405 			});
12406 
12407 			if (s.more_colors_func) {
12408 				n = DOM.add(tb, 'tr');
12409 				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
12410 				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
12411 
12412 				Event.add(n, 'click', function(e) {
12413 					s.more_colors_func.call(s.more_colors_scope || this);
12414 					return Event.cancel(e); // Cancel to fix onbeforeunload problem
12415 				});
12416 			}
12417 
12418 			DOM.addClass(m, 'mceColorSplitMenu');
12419 
12420 			// Prevent IE from scrolling and hindering click to occur #4019
12421 			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
12422 
12423 			Event.add(t.id + '_menu', 'click', function(e) {
12424 				var c;
12425 
12426 				e = DOM.getParent(e.target, 'a', tb);
12427 
12428 				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
12429 					t.setColor(c);
12430 
12431 				return false; // Prevent IE auto save warning
12432 			});
12433 
12434 			return w;
12435 		},
12436 
12437 		setColor : function(c) {
12438 			this.displayColor(c);
12439 			this.hideMenu();
12440 			this.settings.onselect(c);
12441 		},
12442 		
12443 		displayColor : function(c) {
12444 			var t = this;
12445 
12446 			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
12447 
12448 			t.value = c;
12449 		},
12450 
12451 		postRender : function() {
12452 			var t = this, id = t.id;
12453 
12454 			t.parent();
12455 			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
12456 			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
12457 		},
12458 
12459 		destroy : function() {
12460 			var self = this;
12461 
12462 			self.parent();
12463 
12464 			Event.clear(self.id + '_menu');
12465 			Event.clear(self.id + '_more');
12466 			DOM.remove(self.id + '_menu');
12467 
12468 			if (self.keyboardNav) {
12469 				self.keyboardNav.destroy();
12470 			}
12471 		}
12472 	});
12473 })(tinymce);
12474 
12475 (function(tinymce) {
12476 // Shorten class names
12477 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
12478 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
12479 	renderHTML : function() {
12480 		var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
12481 
12482 		h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
12483 		//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
12484 		h.push("<span role='application'>");
12485 		h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
12486 		each(controls, function(toolbar) {
12487 			h.push(toolbar.renderHTML());
12488 		});
12489 		h.push("</span>");
12490 		h.push('</div>');
12491 
12492 		return h.join('');
12493 	},
12494 	
12495 	focus : function() {
12496 		var t = this;
12497 		dom.get(t.id).focus();
12498 	},
12499 	
12500 	postRender : function() {
12501 		var t = this, items = [];
12502 
12503 		each(t.controls, function(toolbar) {
12504 			each (toolbar.controls, function(control) {
12505 				if (control.id) {
12506 					items.push(control);
12507 				}
12508 			});
12509 		});
12510 
12511 		t.keyNav = new tinymce.ui.KeyboardNavigation({
12512 			root: t.id,
12513 			items: items,
12514 			onCancel: function() {
12515 				//Move focus if webkit so that navigation back will read the item.
12516 				if (tinymce.isWebKit) {
12517 					dom.get(t.editor.id+"_ifr").focus();
12518 				}
12519 				t.editor.focus();
12520 			},
12521 			excludeFromTabOrder: !t.settings.tab_focus_toolbar
12522 		});
12523 	},
12524 	
12525 	destroy : function() {
12526 		var self = this;
12527 
12528 		self.parent();
12529 		self.keyNav.destroy();
12530 		Event.clear(self.id);
12531 	}
12532 });
12533 })(tinymce);
12534 
12535 (function(tinymce) {
12536 // Shorten class names
12537 var dom = tinymce.DOM, each = tinymce.each;
12538 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12539 	renderHTML : function() {
12540 		var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
12541 
12542 		cl = t.controls;
12543 		for (i=0; i<cl.length; i++) {
12544 			// Get current control, prev control, next control and if the control is a list box or not
12545 			co = cl[i];
12546 			pr = cl[i - 1];
12547 			nx = cl[i + 1];
12548 
12549 			// Add toolbar start
12550 			if (i === 0) {
12551 				c = 'mceToolbarStart';
12552 
12553 				if (co.Button)
12554 					c += ' mceToolbarStartButton';
12555 				else if (co.SplitButton)
12556 					c += ' mceToolbarStartSplitButton';
12557 				else if (co.ListBox)
12558 					c += ' mceToolbarStartListBox';
12559 
12560 				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12561 			}
12562 
12563 			// Add toolbar end before list box and after the previous button
12564 			// This is to fix the o2k7 editor skins
12565 			if (pr && co.ListBox) {
12566 				if (pr.Button || pr.SplitButton)
12567 					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
12568 			}
12569 
12570 			// Render control HTML
12571 
12572 			// IE 8 quick fix, needed to propertly generate a hit area for anchors
12573 			if (dom.stdMode)
12574 				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
12575 			else
12576 				h += '<td>' + co.renderHTML() + '</td>';
12577 
12578 			// Add toolbar start after list box and before the next button
12579 			// This is to fix the o2k7 editor skins
12580 			if (nx && co.ListBox) {
12581 				if (nx.Button || nx.SplitButton)
12582 					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
12583 			}
12584 		}
12585 
12586 		c = 'mceToolbarEnd';
12587 
12588 		if (co.Button)
12589 			c += ' mceToolbarEndButton';
12590 		else if (co.SplitButton)
12591 			c += ' mceToolbarEndSplitButton';
12592 		else if (co.ListBox)
12593 			c += ' mceToolbarEndListBox';
12594 
12595 		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12596 
12597 		return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
12598 	}
12599 });
12600 })(tinymce);
12601 
12602 (function(tinymce) {
12603 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
12604 
12605 	tinymce.create('tinymce.AddOnManager', {
12606 		AddOnManager : function() {
12607 			var self = this;
12608 
12609 			self.items = [];
12610 			self.urls = {};
12611 			self.lookup = {};
12612 			self.onAdd = new Dispatcher(self);
12613 		},
12614 
12615 		get : function(n) {
12616 			if (this.lookup[n]) {
12617 				return this.lookup[n].instance;
12618 			} else {
12619 				return undefined;
12620 			}
12621 		},
12622 
12623 		dependencies : function(n) {
12624 			var result;
12625 			if (this.lookup[n]) {
12626 				result = this.lookup[n].dependencies;
12627 			}
12628 			return result || [];
12629 		},
12630 
12631 		requireLangPack : function(n) {
12632 			var s = tinymce.settings;
12633 
12634 			if (s && s.language && s.language_load !== false)
12635 				tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
12636 		},
12637 
12638 		add : function(id, o, dependencies) {
12639 			this.items.push(o);
12640 			this.lookup[id] = {instance:o, dependencies:dependencies};
12641 			this.onAdd.dispatch(this, id, o);
12642 
12643 			return o;
12644 		},
12645 		createUrl: function(baseUrl, dep) {
12646 			if (typeof dep === "object") {
12647 				return dep
12648 			} else {
12649 				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
12650 			}
12651 		},
12652 
12653 		addComponents: function(pluginName, scripts) {
12654 			var pluginUrl = this.urls[pluginName];
12655 			tinymce.each(scripts, function(script){
12656 				tinymce.ScriptLoader.add(pluginUrl+"/"+script);	
12657 			});
12658 		},
12659 
12660 		load : function(n, u, cb, s) {
12661 			var t = this, url = u;
12662 
12663 			function loadDependencies() {
12664 				var dependencies = t.dependencies(n);
12665 				tinymce.each(dependencies, function(dep) {
12666 					var newUrl = t.createUrl(u, dep);
12667 					t.load(newUrl.resource, newUrl, undefined, undefined);
12668 				});
12669 				if (cb) {
12670 					if (s) {
12671 						cb.call(s);
12672 					} else {
12673 						cb.call(tinymce.ScriptLoader);
12674 					}
12675 				}
12676 			}
12677 
12678 			if (t.urls[n])
12679 				return;
12680 			if (typeof u === "object")
12681 				url = u.prefix + u.resource + u.suffix;
12682 
12683 			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
12684 				url = tinymce.baseURL + '/' + url;
12685 
12686 			t.urls[n] = url.substring(0, url.lastIndexOf('/'));
12687 
12688 			if (t.lookup[n]) {
12689 				loadDependencies();
12690 			} else {
12691 				tinymce.ScriptLoader.add(url, loadDependencies, s);
12692 			}
12693 		}
12694 	});
12695 
12696 	// Create plugin and theme managers
12697 	tinymce.PluginManager = new tinymce.AddOnManager();
12698 	tinymce.ThemeManager = new tinymce.AddOnManager();
12699 }(tinymce));
12700 
12701 (function(tinymce) {
12702 	// Shorten names
12703 	var each = tinymce.each, extend = tinymce.extend,
12704 		DOM = tinymce.DOM, Event = tinymce.dom.Event,
12705 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
12706 		explode = tinymce.explode,
12707 		Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
12708 
12709 	// Setup some URLs where the editor API is located and where the document is
12710 	tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
12711 	if (!/[\/\\]$/.test(tinymce.documentBaseURL))
12712 		tinymce.documentBaseURL += '/';
12713 
12714 	tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
12715 
12716 	tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
12717 
12718 	// Add before unload listener
12719 	// This was required since IE was leaking memory if you added and removed beforeunload listeners
12720 	// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
12721 	tinymce.onBeforeUnload = new Dispatcher(tinymce);
12722 
12723 	// Must be on window or IE will leak if the editor is placed in frame or iframe
12724 	Event.add(window, 'beforeunload', function(e) {
12725 		tinymce.onBeforeUnload.dispatch(tinymce, e);
12726 	});
12727 
12728 	tinymce.onAddEditor = new Dispatcher(tinymce);
12729 
12730 	tinymce.onRemoveEditor = new Dispatcher(tinymce);
12731 
12732 	tinymce.EditorManager = extend(tinymce, {
12733 		editors : [],
12734 
12735 		i18n : {},
12736 
12737 		activeEditor : null,
12738 
12739 		init : function(s) {
12740 			var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
12741 
12742 			function createId(elm) {
12743 				var id = elm.id;
12744 	
12745 				// Use element id, or unique name or generate a unique id
12746 				if (!id) {
12747 					id = elm.name;
12748 	
12749 					if (id && !DOM.get(id)) {
12750 						id = elm.name;
12751 					} else {
12752 						// Generate unique name
12753 						id = DOM.uniqueId();
12754 					}
12755 
12756 					elm.setAttribute('id', id);
12757 				}
12758 
12759 				return id;
12760 			};
12761 
12762 			function execCallback(se, n, s) {
12763 				var f = se[n];
12764 
12765 				if (!f)
12766 					return;
12767 
12768 				if (tinymce.is(f, 'string')) {
12769 					s = f.replace(/\.\w+$/, '');
12770 					s = s ? tinymce.resolve(s) : 0;
12771 					f = tinymce.resolve(f);
12772 				}
12773 
12774 				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
12775 			};
12776 
12777 			function hasClass(n, c) {
12778 				return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
12779 			};
12780 
12781 			t.settings = s;
12782 
12783 			// Legacy call
12784 			Event.bind(window, 'ready', function() {
12785 				var l, co;
12786 
12787 				execCallback(s, 'onpageload');
12788 
12789 				switch (s.mode) {
12790 					case "exact":
12791 						l = s.elements || '';
12792 
12793 						if(l.length > 0) {
12794 							each(explode(l), function(v) {
12795 								if (DOM.get(v)) {
12796 									ed = new tinymce.Editor(v, s);
12797 									el.push(ed);
12798 									ed.render(1);
12799 								} else {
12800 									each(document.forms, function(f) {
12801 										each(f.elements, function(e) {
12802 											if (e.name === v) {
12803 												v = 'mce_editor_' + instanceCounter++;
12804 												DOM.setAttrib(e, 'id', v);
12805 
12806 												ed = new tinymce.Editor(v, s);
12807 												el.push(ed);
12808 												ed.render(1);
12809 											}
12810 										});
12811 									});
12812 								}
12813 							});
12814 						}
12815 						break;
12816 
12817 					case "textareas":
12818 					case "specific_textareas":
12819 						each(DOM.select('textarea'), function(elm) {
12820 							if (s.editor_deselector && hasClass(elm, s.editor_deselector))
12821 								return;
12822 
12823 							if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
12824 								ed = new tinymce.Editor(createId(elm), s);
12825 								el.push(ed);
12826 								ed.render(1);
12827 							}
12828 						});
12829 						break;
12830 					
12831 					default:
12832 						if (s.types) {
12833 							// Process type specific selector
12834 							each(s.types, function(type) {
12835 								each(DOM.select(type.selector), function(elm) {
12836 									var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
12837 									el.push(editor);
12838 									editor.render(1);
12839 								});
12840 							});
12841 						} else if (s.selector) {
12842 							// Process global selector
12843 							each(DOM.select(s.selector), function(elm) {
12844 								var editor = new tinymce.Editor(createId(elm), s);
12845 								el.push(editor);
12846 								editor.render(1);
12847 							});
12848 						}
12849 				}
12850 
12851 				// Call onInit when all editors are initialized
12852 				if (s.oninit) {
12853 					l = co = 0;
12854 
12855 					each(el, function(ed) {
12856 						co++;
12857 
12858 						if (!ed.initialized) {
12859 							// Wait for it
12860 							ed.onInit.add(function() {
12861 								l++;
12862 
12863 								// All done
12864 								if (l == co)
12865 									execCallback(s, 'oninit');
12866 							});
12867 						} else
12868 							l++;
12869 
12870 						// All done
12871 						if (l == co)
12872 							execCallback(s, 'oninit');					
12873 					});
12874 				}
12875 			});
12876 		},
12877 
12878 		get : function(id) {
12879 			if (id === undef)
12880 				return this.editors;
12881 
12882 			return this.editors[id];
12883 		},
12884 
12885 		getInstanceById : function(id) {
12886 			return this.get(id);
12887 		},
12888 
12889 		add : function(editor) {
12890 			var self = this, editors = self.editors;
12891 
12892 			// Add named and index editor instance
12893 			editors[editor.id] = editor;
12894 			editors.push(editor);
12895 
12896 			self._setActive(editor);
12897 			self.onAddEditor.dispatch(self, editor);
12898 
12899 
12900 			return editor;
12901 		},
12902 
12903 		remove : function(editor) {
12904 			var t = this, i, editors = t.editors;
12905 
12906 			// Not in the collection
12907 			if (!editors[editor.id])
12908 				return null;
12909 
12910 			delete editors[editor.id];
12911 
12912 			for (i = 0; i < editors.length; i++) {
12913 				if (editors[i] == editor) {
12914 					editors.splice(i, 1);
12915 					break;
12916 				}
12917 			}
12918 
12919 			// Select another editor since the active one was removed
12920 			if (t.activeEditor == editor)
12921 				t._setActive(editors[0]);
12922 
12923 			editor.destroy();
12924 			t.onRemoveEditor.dispatch(t, editor);
12925 
12926 			return editor;
12927 		},
12928 
12929 		execCommand : function(c, u, v) {
12930 			var t = this, ed = t.get(v), w;
12931 
12932 			function clr() {
12933 				ed.destroy();
12934 				w.detachEvent('onunload', clr);
12935 				w = w.tinyMCE = w.tinymce = null; // IE leak
12936 			};
12937 
12938 			// Manager commands
12939 			switch (c) {
12940 				case "mceFocus":
12941 					ed.focus();
12942 					return true;
12943 
12944 				case "mceAddEditor":
12945 				case "mceAddControl":
12946 					if (!t.get(v))
12947 						new tinymce.Editor(v, t.settings).render();
12948 
12949 					return true;
12950 
12951 				case "mceAddFrameControl":
12952 					w = v.window;
12953 
12954 					// Add tinyMCE global instance and tinymce namespace to specified window
12955 					w.tinyMCE = tinyMCE;
12956 					w.tinymce = tinymce;
12957 
12958 					tinymce.DOM.doc = w.document;
12959 					tinymce.DOM.win = w;
12960 
12961 					ed = new tinymce.Editor(v.element_id, v);
12962 					ed.render();
12963 
12964 					// Fix IE memory leaks
12965 					if (tinymce.isIE) {
12966 						w.attachEvent('onunload', clr);
12967 					}
12968 
12969 					v.page_window = null;
12970 
12971 					return true;
12972 
12973 				case "mceRemoveEditor":
12974 				case "mceRemoveControl":
12975 					if (ed)
12976 						ed.remove();
12977 
12978 					return true;
12979 
12980 				case 'mceToggleEditor':
12981 					if (!ed) {
12982 						t.execCommand('mceAddControl', 0, v);
12983 						return true;
12984 					}
12985 
12986 					if (ed.isHidden())
12987 						ed.show();
12988 					else
12989 						ed.hide();
12990 
12991 					return true;
12992 			}
12993 
12994 			// Run command on active editor
12995 			if (t.activeEditor)
12996 				return t.activeEditor.execCommand(c, u, v);
12997 
12998 			return false;
12999 		},
13000 
13001 		execInstanceCommand : function(id, c, u, v) {
13002 			var ed = this.get(id);
13003 
13004 			if (ed)
13005 				return ed.execCommand(c, u, v);
13006 
13007 			return false;
13008 		},
13009 
13010 		triggerSave : function() {
13011 			each(this.editors, function(e) {
13012 				e.save();
13013 			});
13014 		},
13015 
13016 		addI18n : function(p, o) {
13017 			var lo, i18n = this.i18n;
13018 
13019 			if (!tinymce.is(p, 'string')) {
13020 				each(p, function(o, lc) {
13021 					each(o, function(o, g) {
13022 						each(o, function(o, k) {
13023 							if (g === 'common')
13024 								i18n[lc + '.' + k] = o;
13025 							else
13026 								i18n[lc + '.' + g + '.' + k] = o;
13027 						});
13028 					});
13029 				});
13030 			} else {
13031 				each(o, function(o, k) {
13032 					i18n[p + '.' + k] = o;
13033 				});
13034 			}
13035 		},
13036 
13037 		// Private methods
13038 
13039 		_setActive : function(editor) {
13040 			this.selectedInstance = this.activeEditor = editor;
13041 		}
13042 	});
13043 })(tinymce);
13044 
13045 (function(tinymce) {
13046 	// Shorten these names
13047 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
13048 		each = tinymce.each, isGecko = tinymce.isGecko,
13049 		isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
13050 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13051 		explode = tinymce.explode;
13052 
13053 	tinymce.create('tinymce.Editor', {
13054 		Editor : function(id, settings) {
13055 			var self = this, TRUE = true;
13056 
13057 			self.settings = settings = extend({
13058 				id : id,
13059 				language : 'en',
13060 				theme : 'advanced',
13061 				skin : 'default',
13062 				delta_width : 0,
13063 				delta_height : 0,
13064 				popup_css : '',
13065 				plugins : '',
13066 				document_base_url : tinymce.documentBaseURL,
13067 				add_form_submit_trigger : TRUE,
13068 				submit_patch : TRUE,
13069 				add_unload_trigger : TRUE,
13070 				convert_urls : TRUE,
13071 				relative_urls : TRUE,
13072 				remove_script_host : TRUE,
13073 				table_inline_editing : false,
13074 				object_resizing : TRUE,
13075 				accessibility_focus : TRUE,
13076 				doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
13077 				visual : TRUE,
13078 				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
13079 				font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
13080 				apply_source_formatting : TRUE,
13081 				directionality : 'ltr',
13082 				forced_root_block : 'p',
13083 				hidden_input : TRUE,
13084 				padd_empty_editor : TRUE,
13085 				render_ui : TRUE,
13086 				indentation : '30px',
13087 				fix_table_elements : TRUE,
13088 				inline_styles : TRUE,
13089 				convert_fonts_to_spans : TRUE,
13090 				indent : 'simple',
13091 				indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13092 				indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13093 				validate : TRUE,
13094 				entity_encoding : 'named',
13095 				url_converter : self.convertURL,
13096 				url_converter_scope : self,
13097 				ie7_compat : TRUE
13098 			}, settings);
13099 
13100 			self.id = self.editorId = id;
13101 
13102 			self.isNotDirty = false;
13103 
13104 			self.plugins = {};
13105 
13106 			self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
13107 				base_uri : tinyMCE.baseURI
13108 			});
13109 
13110 			self.baseURI = tinymce.baseURI;
13111 
13112 			self.contentCSS = [];
13113 
13114 			self.contentStyles = [];
13115 
13116 			// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
13117 			self.setupEvents();
13118 
13119 			// Internal command handler objects
13120 			self.execCommands = {};
13121 			self.queryStateCommands = {};
13122 			self.queryValueCommands = {};
13123 
13124 			// Call setup
13125 			self.execCallback('setup', self);
13126 		},
13127 
13128 		render : function(nst) {
13129 			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
13130 
13131 			// Page is not loaded yet, wait for it
13132 			if (!Event.domLoaded) {
13133 				Event.add(window, 'ready', function() {
13134 					t.render();
13135 				});
13136 				return;
13137 			}
13138 
13139 			tinyMCE.settings = s;
13140 
13141 			// Element not found, then skip initialization
13142 			if (!t.getElement())
13143 				return;
13144 
13145 			// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
13146 			// here since the browser says it has contentEditable support but there is no visible caret.
13147 			if (tinymce.isIDevice && !tinymce.isIOS5)
13148 				return;
13149 
13150 			// Add hidden input for non input elements inside form elements
13151 			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
13152 				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
13153 
13154 			// Hide target element early to prevent content flashing
13155 			if (!s.content_editable) {
13156 				t.orgVisibility = t.getElement().style.visibility;
13157 				t.getElement().style.visibility = 'hidden';
13158 			}
13159 
13160 			if (tinymce.WindowManager)
13161 				t.windowManager = new tinymce.WindowManager(t);
13162 
13163 			if (s.encoding == 'xml') {
13164 				t.onGetContent.add(function(ed, o) {
13165 					if (o.save)
13166 						o.content = DOM.encode(o.content);
13167 				});
13168 			}
13169 
13170 			if (s.add_form_submit_trigger) {
13171 				t.onSubmit.addToTop(function() {
13172 					if (t.initialized) {
13173 						t.save();
13174 						t.isNotDirty = 1;
13175 					}
13176 				});
13177 			}
13178 
13179 			if (s.add_unload_trigger) {
13180 				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
13181 					if (t.initialized && !t.destroyed && !t.isHidden())
13182 						t.save({format : 'raw', no_events : true});
13183 				});
13184 			}
13185 
13186 			tinymce.addUnload(t.destroy, t);
13187 
13188 			if (s.submit_patch) {
13189 				t.onBeforeRenderUI.add(function() {
13190 					var n = t.getElement().form;
13191 
13192 					if (!n)
13193 						return;
13194 
13195 					// Already patched
13196 					if (n._mceOldSubmit)
13197 						return;
13198 
13199 					// Check page uses id="submit" or name="submit" for it's submit button
13200 					if (!n.submit.nodeType && !n.submit.length) {
13201 						t.formElement = n;
13202 						n._mceOldSubmit = n.submit;
13203 						n.submit = function() {
13204 							// Save all instances
13205 							tinymce.triggerSave();
13206 							t.isNotDirty = 1;
13207 
13208 							return t.formElement._mceOldSubmit(t.formElement);
13209 						};
13210 					}
13211 
13212 					n = null;
13213 				});
13214 			}
13215 
13216 			// Load scripts
13217 			function loadScripts() {
13218 				if (s.language && s.language_load !== false)
13219 					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
13220 
13221 				if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
13222 					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
13223 
13224 				each(explode(s.plugins), function(p) {
13225 					if (p &&!PluginManager.urls[p]) {
13226 						if (p.charAt(0) == '-') {
13227 							p = p.substr(1, p.length);
13228 							var dependencies = PluginManager.dependencies(p);
13229 							each(dependencies, function(dep) {
13230 								var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
13231 								dep = PluginManager.createUrl(defaultSettings, dep);
13232 								PluginManager.load(dep.resource, dep);
13233 							});
13234 						} else {
13235 							// Skip safari plugin, since it is removed as of 3.3b1
13236 							if (p == 'safari') {
13237 								return;
13238 							}
13239 							PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
13240 						}
13241 					}
13242 				});
13243 
13244 				// Init when que is loaded
13245 				sl.loadQueue(function() {
13246 					if (!t.removed)
13247 						t.init();
13248 				});
13249 			};
13250 
13251 			loadScripts();
13252 		},
13253 
13254 		init : function() {
13255 			var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
13256 
13257 			tinymce.add(t);
13258 
13259 			s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
13260 
13261 			if (s.theme) {
13262 				if (typeof s.theme != "function") {
13263 					s.theme = s.theme.replace(/-/, '');
13264 					o = ThemeManager.get(s.theme);
13265 					t.theme = new o();
13266 
13267 					if (t.theme.init)
13268 						t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
13269 				} else {
13270 					t.theme = s.theme;
13271 				}
13272 			}
13273 
13274 			function initPlugin(p) {
13275 				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
13276 				if (c && tinymce.inArray(initializedPlugins,p) === -1) {
13277 					each(PluginManager.dependencies(p), function(dep){
13278 						initPlugin(dep);
13279 					});
13280 					po = new c(t, u);
13281 
13282 					t.plugins[p] = po;
13283 
13284 					if (po.init) {
13285 						po.init(t, u);
13286 						initializedPlugins.push(p);
13287 					}
13288 				}
13289 			}
13290 			
13291 			// Create all plugins
13292 			each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
13293 
13294 			// Setup popup CSS path(s)
13295 			if (s.popup_css !== false) {
13296 				if (s.popup_css)
13297 					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
13298 				else
13299 					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
13300 			}
13301 
13302 			if (s.popup_css_add)
13303 				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
13304 
13305 			t.controlManager = new tinymce.ControlManager(t);
13306 
13307 			// Enables users to override the control factory
13308 			t.onBeforeRenderUI.dispatch(t, t.controlManager);
13309 
13310 			// Measure box
13311 			if (s.render_ui && t.theme) {
13312 				t.orgDisplay = e.style.display;
13313 
13314 				if (typeof s.theme != "function") {
13315 					w = s.width || e.style.width || e.offsetWidth;
13316 					h = s.height || e.style.height || e.offsetHeight;
13317 					mh = s.min_height || 100;
13318 					re = /^[0-9\.]+(|px)$/i;
13319 
13320 					if (re.test('' + w))
13321 						w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
13322 
13323 					if (re.test('' + h))
13324 						h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
13325 
13326 					// Render UI
13327 					o = t.theme.renderUI({
13328 						targetNode : e,
13329 						width : w,
13330 						height : h,
13331 						deltaWidth : s.delta_width,
13332 						deltaHeight : s.delta_height
13333 					});
13334 
13335 					// Resize editor
13336 					DOM.setStyles(o.sizeContainer || o.editorContainer, {
13337 						width : w,
13338 						height : h
13339 					});
13340 
13341 					h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
13342 					if (h < mh)
13343 						h = mh;
13344 				} else {
13345 					o = s.theme(t, e);
13346 
13347 					// Convert element type to id:s
13348 					if (o.editorContainer.nodeType) {
13349 						o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
13350 					}
13351 
13352 					// Convert element type to id:s
13353 					if (o.iframeContainer.nodeType) {
13354 						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
13355 					}
13356 
13357 					// Use specified iframe height or the targets offsetHeight
13358 					h = o.iframeHeight || e.offsetHeight;
13359 
13360 					// Store away the selection when it's changed to it can be restored later with a editor.focus() call
13361 					if (isIE) {
13362 						t.onInit.add(function(ed) {
13363 							ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
13364 								ed.lastIERng = ed.selection.getRng();
13365 							});
13366 						});
13367 					}
13368 				}
13369 
13370 				t.editorContainer = o.editorContainer;
13371 			}
13372 
13373 			// Load specified content CSS last
13374 			if (s.content_css) {
13375 				each(explode(s.content_css), function(u) {
13376 					t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
13377 				});
13378 			}
13379 
13380 			// Content editable mode ends here
13381 			if (s.content_editable) {
13382 				e = n = o = null; // Fix IE leak
13383 				return t.initContentBody();
13384 			}
13385 
13386 			// User specified a document.domain value
13387 			if (document.domain && location.hostname != document.domain)
13388 				tinymce.relaxedDomain = document.domain;
13389 
13390 			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
13391 
13392 			// We only need to override paths if we have to
13393 			// IE has a bug where it remove site absolute urls to relative ones if this is specified
13394 			if (s.document_base_url != tinymce.documentBaseURL)
13395 				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
13396 
13397 			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
13398 			if (s.ie7_compat)
13399 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
13400 			else
13401 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
13402 
13403 			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
13404 
13405 			// Load the CSS by injecting them into the HTML this will reduce "flicker"
13406 			for (i = 0; i < t.contentCSS.length; i++) {
13407 				t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
13408 			}
13409 
13410 			t.contentCSS = [];
13411 
13412 			bi = s.body_id || 'tinymce';
13413 			if (bi.indexOf('=') != -1) {
13414 				bi = t.getParam('body_id', '', 'hash');
13415 				bi = bi[t.id] || bi;
13416 			}
13417 
13418 			bc = s.body_class || '';
13419 			if (bc.indexOf('=') != -1) {
13420 				bc = t.getParam('body_class', '', 'hash');
13421 				bc = bc[t.id] || '';
13422 			}
13423 
13424 			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
13425 
13426 			// Domain relaxing enabled, then set document domain
13427 			if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
13428 				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
13429 				u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
13430 			}
13431 
13432 			// Create iframe
13433 			// TODO: ACC add the appropriate description on this.
13434 			n = DOM.add(o.iframeContainer, 'iframe', { 
13435 				id : t.id + "_ifr",
13436 				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
13437 				frameBorder : '0',
13438 				allowTransparency : "true",
13439 				title : s.aria_label,
13440 				style : {
13441 					width : '100%',
13442 					height : h,
13443 					display : 'block' // Important for Gecko to render the iframe correctly
13444 				}
13445 			});
13446 
13447 			t.contentAreaContainer = o.iframeContainer;
13448 
13449 			if (o.editorContainer) {
13450 				DOM.get(o.editorContainer).style.display = t.orgDisplay;
13451 			}
13452 
13453 			// Restore visibility on target element
13454 			e.style.visibility = t.orgVisibility;
13455 
13456 			DOM.get(t.id).style.display = 'none';
13457 			DOM.setAttrib(t.id, 'aria-hidden', true);
13458 
13459 			if (!tinymce.relaxedDomain || !u)
13460 				t.initContentBody();
13461 
13462 			e = n = o = null; // Cleanup
13463 		},
13464 
13465 		initContentBody : function() {
13466 			var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
13467 
13468 			// Setup iframe body
13469 			if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
13470 				doc.open();
13471 				doc.write(self.iframeHTML);
13472 				doc.close();
13473 
13474 				if (tinymce.relaxedDomain)
13475 					doc.domain = tinymce.relaxedDomain;
13476 			}
13477 
13478 			if (settings.content_editable) {
13479 				DOM.addClass(targetElm, 'mceContentBody');
13480 				self.contentDocument = doc = settings.content_document || document;
13481 				self.contentWindow = settings.content_window || window;
13482 				self.bodyElement = targetElm;
13483 
13484 				// Prevent leak in IE
13485 				settings.content_document = settings.content_window = null;
13486 			}
13487 
13488 			// It will not steal focus while setting contentEditable
13489 			body = self.getBody();
13490 			body.disabled = true;
13491 
13492 			if (!settings.readonly)
13493 				body.contentEditable = self.getParam('content_editable_state', true);
13494 
13495 			body.disabled = false;
13496 
13497 			self.schema = new tinymce.html.Schema(settings);
13498 
13499 			self.dom = new tinymce.dom.DOMUtils(doc, {
13500 				keep_values : true,
13501 				url_converter : self.convertURL,
13502 				url_converter_scope : self,
13503 				hex_colors : settings.force_hex_style_colors,
13504 				class_filter : settings.class_filter,
13505 				update_styles : true,
13506 				root_element : settings.content_editable ? self.id : null,
13507 				schema : self.schema
13508 			});
13509 
13510 			self.parser = new tinymce.html.DomParser(settings, self.schema);
13511 
13512 			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
13513 			self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
13514 				var i = nodes.length, node, dom = self.dom, value, internalName;
13515 
13516 				while (i--) {
13517 					node = nodes[i];
13518 					value = node.attr(name);
13519 					internalName = 'data-mce-' + name;
13520 
13521 					// Add internal attribute if we need to we don't on a refresh of the document
13522 					if (!node.attributes.map[internalName]) {	
13523 						if (name === "style")
13524 							node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
13525 						else
13526 							node.attr(internalName, self.convertURL(value, name, node.name));
13527 					}
13528 				}
13529 			});
13530 
13531 			// Keep scripts from executing
13532 			self.parser.addNodeFilter('script', function(nodes, name) {
13533 				var i = nodes.length, node;
13534 
13535 				while (i--) {
13536 					node = nodes[i];
13537 					node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
13538 				}
13539 			});
13540 
13541 			self.parser.addNodeFilter('#cdata', function(nodes, name) {
13542 				var i = nodes.length, node;
13543 
13544 				while (i--) {
13545 					node = nodes[i];
13546 					node.type = 8;
13547 					node.name = '#comment';
13548 					node.value = '[CDATA[' + node.value + ']]';
13549 				}
13550 			});
13551 
13552 			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
13553 				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
13554 
13555 				while (i--) {
13556 					node = nodes[i];
13557 
13558 					if (node.isEmpty(nonEmptyElements))
13559 						node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
13560 				}
13561 			});
13562 
13563 			self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
13564 
13565 			self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
13566 
13567 			self.formatter = new tinymce.Formatter(self);
13568 
13569 			self.undoManager = new tinymce.UndoManager(self);
13570 
13571 			self.forceBlocks = new tinymce.ForceBlocks(self);
13572 			self.enterKey = new tinymce.EnterKey(self);
13573 			self.editorCommands = new tinymce.EditorCommands(self);
13574 
13575 			self.onExecCommand.add(function(editor, command) {
13576 				// Don't refresh the select lists until caret move
13577 				if (!/^(FontName|FontSize)$/.test(command))
13578 					self.nodeChanged();
13579 			});
13580 
13581 			// Pass through
13582 			self.serializer.onPreProcess.add(function(se, o) {
13583 				return self.onPreProcess.dispatch(self, o, se);
13584 			});
13585 
13586 			self.serializer.onPostProcess.add(function(se, o) {
13587 				return self.onPostProcess.dispatch(self, o, se);
13588 			});
13589 
13590 			self.onPreInit.dispatch(self);
13591 
13592 			if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
13593 				doc.body.spellcheck = false;
13594 
13595 			if (!settings.readonly) {
13596 				self.bindNativeEvents();
13597 			}
13598 
13599 			self.controlManager.onPostRender.dispatch(self, self.controlManager);
13600 			self.onPostRender.dispatch(self);
13601 
13602 			self.quirks = tinymce.util.Quirks(self);
13603 
13604 			if (settings.directionality)
13605 				body.dir = settings.directionality;
13606 
13607 			if (settings.nowrap)
13608 				body.style.whiteSpace = "nowrap";
13609 
13610 			if (settings.protect) {
13611 				self.onBeforeSetContent.add(function(ed, o) {
13612 					each(settings.protect, function(pattern) {
13613 						o.content = o.content.replace(pattern, function(str) {
13614 							return '<!--mce:protected ' + escape(str) + '-->';
13615 						});
13616 					});
13617 				});
13618 			}
13619 
13620 			// Add visual aids when new contents is added
13621 			self.onSetContent.add(function() {
13622 				self.addVisual(self.getBody());
13623 			});
13624 
13625 			// Remove empty contents
13626 			if (settings.padd_empty_editor) {
13627 				self.onPostProcess.add(function(ed, o) {
13628 					o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
13629 				});
13630 			}
13631 
13632 			self.load({initial : true, format : 'html'});
13633 			self.startContent = self.getContent({format : 'raw'});
13634 
13635 			self.initialized = true;
13636 
13637 			self.onInit.dispatch(self);
13638 			self.execCallback('setupcontent_callback', self.id, body, doc);
13639 			self.execCallback('init_instance_callback', self);
13640 			self.focus(true);
13641 			self.nodeChanged({initial : true});
13642 
13643 			// Add editor specific CSS styles
13644 			if (self.contentStyles.length > 0) {
13645 				contentCssText = '';
13646 
13647 				each(self.contentStyles, function(style) {
13648 					contentCssText += style + "\r\n";
13649 				});
13650 
13651 				self.dom.addStyle(contentCssText);
13652 			}
13653 
13654 			// Load specified content CSS last
13655 			each(self.contentCSS, function(url) {
13656 				self.dom.loadCSS(url);
13657 			});
13658 
13659 			// Handle auto focus
13660 			if (settings.auto_focus) {
13661 				setTimeout(function () {
13662 					var ed = tinymce.get(settings.auto_focus);
13663 
13664 					ed.selection.select(ed.getBody(), 1);
13665 					ed.selection.collapse(1);
13666 					ed.getBody().focus();
13667 					ed.getWin().focus();
13668 				}, 100);
13669 			}
13670 
13671 			// Clean up references for IE
13672 			targetElm = doc = body = null;
13673 		},
13674 
13675 		focus : function(skip_focus) {
13676 			var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
13677 
13678 			if (!skip_focus) {
13679 				if (self.lastIERng) {
13680 					selection.setRng(self.lastIERng);
13681 				}
13682 
13683 				// Get selected control element
13684 				ieRng = selection.getRng();
13685 				if (ieRng.item) {
13686 					controlElm = ieRng.item(0);
13687 				}
13688 
13689 				self._refreshContentEditable();
13690 
13691 				// Focus the window iframe
13692 				if (!contentEditable) {
13693 					self.getWin().focus();
13694 				}
13695 
13696 				// Focus the body as well since it's contentEditable
13697 				if (tinymce.isGecko || contentEditable) {
13698 					body = self.getBody();
13699 
13700 					// Check for setActive since it doesn't scroll to the element
13701 					if (body.setActive) {
13702 						body.setActive();
13703 					} else {
13704 						body.focus();
13705 					}
13706 
13707 					if (contentEditable) {
13708 						selection.normalize();
13709 					}
13710 				}
13711 
13712 				// Restore selected control element
13713 				// This is needed when for example an image is selected within a
13714 				// layer a call to focus will then remove the control selection
13715 				if (controlElm && controlElm.ownerDocument == doc) {
13716 					ieRng = doc.body.createControlRange();
13717 					ieRng.addElement(controlElm);
13718 					ieRng.select();
13719 				}
13720 			}
13721 
13722 			if (tinymce.activeEditor != self) {
13723 				if ((oed = tinymce.activeEditor) != null)
13724 					oed.onDeactivate.dispatch(oed, self);
13725 
13726 				self.onActivate.dispatch(self, oed);
13727 			}
13728 
13729 			tinymce._setActive(self);
13730 		},
13731 
13732 		execCallback : function(n) {
13733 			var t = this, f = t.settings[n], s;
13734 
13735 			if (!f)
13736 				return;
13737 
13738 			// Look through lookup
13739 			if (t.callbackLookup && (s = t.callbackLookup[n])) {
13740 				f = s.func;
13741 				s = s.scope;
13742 			}
13743 
13744 			if (is(f, 'string')) {
13745 				s = f.replace(/\.\w+$/, '');
13746 				s = s ? tinymce.resolve(s) : 0;
13747 				f = tinymce.resolve(f);
13748 				t.callbackLookup = t.callbackLookup || {};
13749 				t.callbackLookup[n] = {func : f, scope : s};
13750 			}
13751 
13752 			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
13753 		},
13754 
13755 		translate : function(s) {
13756 			var c = this.settings.language || 'en', i18n = tinymce.i18n;
13757 
13758 			if (!s)
13759 				return '';
13760 
13761 			return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
13762 				return i18n[c + '.' + b] || '{#' + b + '}';
13763 			});
13764 		},
13765 
13766 		getLang : function(n, dv) {
13767 			return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
13768 		},
13769 
13770 		getParam : function(n, dv, ty) {
13771 			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
13772 
13773 			if (ty === 'hash') {
13774 				o = {};
13775 
13776 				if (is(v, 'string')) {
13777 					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
13778 						v = v.split('=');
13779 
13780 						if (v.length > 1)
13781 							o[tr(v[0])] = tr(v[1]);
13782 						else
13783 							o[tr(v[0])] = tr(v);
13784 					});
13785 				} else
13786 					o = v;
13787 
13788 				return o;
13789 			}
13790 
13791 			return v;
13792 		},
13793 
13794 		nodeChanged : function(o) {
13795 			var self = this, selection = self.selection, node;
13796 
13797 			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
13798 			if (self.initialized) {
13799 				o = o || {};
13800 
13801 				// Get start node
13802 				node = selection.getStart() || self.getBody();
13803 				node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
13804 
13805 				// Get parents and add them to object
13806 				o.parents = [];
13807 				self.dom.getParent(node, function(node) {
13808 					if (node.nodeName == 'BODY')
13809 						return true;
13810 
13811 					o.parents.push(node);
13812 				});
13813 
13814 				self.onNodeChange.dispatch(
13815 					self,
13816 					o ? o.controlManager || self.controlManager : self.controlManager,
13817 					node,
13818 					selection.isCollapsed(),
13819 					o
13820 				);
13821 			}
13822 		},
13823 
13824 		addButton : function(name, settings) {
13825 			var self = this;
13826 
13827 			self.buttons = self.buttons || {};
13828 			self.buttons[name] = settings;
13829 		},
13830 
13831 		addCommand : function(name, callback, scope) {
13832 			this.execCommands[name] = {func : callback, scope : scope || this};
13833 		},
13834 
13835 		addQueryStateHandler : function(name, callback, scope) {
13836 			this.queryStateCommands[name] = {func : callback, scope : scope || this};
13837 		},
13838 
13839 		addQueryValueHandler : function(name, callback, scope) {
13840 			this.queryValueCommands[name] = {func : callback, scope : scope || this};
13841 		},
13842 
13843 		addShortcut : function(pa, desc, cmd_func, sc) {
13844 			var t = this, c;
13845 
13846 			if (t.settings.custom_shortcuts === false)
13847 				return false;
13848 
13849 			t.shortcuts = t.shortcuts || {};
13850 
13851 			if (is(cmd_func, 'string')) {
13852 				c = cmd_func;
13853 
13854 				cmd_func = function() {
13855 					t.execCommand(c, false, null);
13856 				};
13857 			}
13858 
13859 			if (is(cmd_func, 'object')) {
13860 				c = cmd_func;
13861 
13862 				cmd_func = function() {
13863 					t.execCommand(c[0], c[1], c[2]);
13864 				};
13865 			}
13866 
13867 			each(explode(pa), function(pa) {
13868 				var o = {
13869 					func : cmd_func,
13870 					scope : sc || this,
13871 					desc : t.translate(desc),
13872 					alt : false,
13873 					ctrl : false,
13874 					shift : false
13875 				};
13876 
13877 				each(explode(pa, '+'), function(v) {
13878 					switch (v) {
13879 						case 'alt':
13880 						case 'ctrl':
13881 						case 'shift':
13882 							o[v] = true;
13883 							break;
13884 
13885 						default:
13886 							o.charCode = v.charCodeAt(0);
13887 							o.keyCode = v.toUpperCase().charCodeAt(0);
13888 					}
13889 				});
13890 
13891 				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
13892 			});
13893 
13894 			return true;
13895 		},
13896 
13897 		execCommand : function(cmd, ui, val, a) {
13898 			var t = this, s = 0, o, st;
13899 
13900 			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
13901 				t.focus();
13902 
13903 			a = extend({}, a);
13904 			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
13905 			if (a.terminate)
13906 				return false;
13907 
13908 			// Command callback
13909 			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
13910 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13911 				return true;
13912 			}
13913 
13914 			// Registred commands
13915 			if (o = t.execCommands[cmd]) {
13916 				st = o.func.call(o.scope, ui, val);
13917 
13918 				// Fall through on true
13919 				if (st !== true) {
13920 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13921 					return st;
13922 				}
13923 			}
13924 
13925 			// Plugin commands
13926 			each(t.plugins, function(p) {
13927 				if (p.execCommand && p.execCommand(cmd, ui, val)) {
13928 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13929 					s = 1;
13930 					return false;
13931 				}
13932 			});
13933 
13934 			if (s)
13935 				return true;
13936 
13937 			// Theme commands
13938 			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
13939 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13940 				return true;
13941 			}
13942 
13943 			// Editor commands
13944 			if (t.editorCommands.execCommand(cmd, ui, val)) {
13945 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13946 				return true;
13947 			}
13948 
13949 			// Browser commands
13950 			t.getDoc().execCommand(cmd, ui, val);
13951 			t.onExecCommand.dispatch(t, cmd, ui, val, a);
13952 		},
13953 
13954 		queryCommandState : function(cmd) {
13955 			var t = this, o, s;
13956 
13957 			// Is hidden then return undefined
13958 			if (t._isHidden())
13959 				return;
13960 
13961 			// Registred commands
13962 			if (o = t.queryStateCommands[cmd]) {
13963 				s = o.func.call(o.scope);
13964 
13965 				// Fall though on true
13966 				if (s !== true)
13967 					return s;
13968 			}
13969 
13970 			// Registred commands
13971 			o = t.editorCommands.queryCommandState(cmd);
13972 			if (o !== -1)
13973 				return o;
13974 
13975 			// Browser commands
13976 			try {
13977 				return this.getDoc().queryCommandState(cmd);
13978 			} catch (ex) {
13979 				// Fails sometimes see bug: 1896577
13980 			}
13981 		},
13982 
13983 		queryCommandValue : function(c) {
13984 			var t = this, o, s;
13985 
13986 			// Is hidden then return undefined
13987 			if (t._isHidden())
13988 				return;
13989 
13990 			// Registred commands
13991 			if (o = t.queryValueCommands[c]) {
13992 				s = o.func.call(o.scope);
13993 
13994 				// Fall though on true
13995 				if (s !== true)
13996 					return s;
13997 			}
13998 
13999 			// Registred commands
14000 			o = t.editorCommands.queryCommandValue(c);
14001 			if (is(o))
14002 				return o;
14003 
14004 			// Browser commands
14005 			try {
14006 				return this.getDoc().queryCommandValue(c);
14007 			} catch (ex) {
14008 				// Fails sometimes see bug: 1896577
14009 			}
14010 		},
14011 
14012 		show : function() {
14013 			var self = this;
14014 
14015 			DOM.show(self.getContainer());
14016 			DOM.hide(self.id);
14017 			self.load();
14018 		},
14019 
14020 		hide : function() {
14021 			var self = this, doc = self.getDoc();
14022 
14023 			// Fixed bug where IE has a blinking cursor left from the editor
14024 			if (isIE && doc)
14025 				doc.execCommand('SelectAll');
14026 
14027 			// We must save before we hide so Safari doesn't crash
14028 			self.save();
14029 			DOM.hide(self.getContainer());
14030 			DOM.setStyle(self.id, 'display', self.orgDisplay);
14031 		},
14032 
14033 		isHidden : function() {
14034 			return !DOM.isHidden(this.id);
14035 		},
14036 
14037 		setProgressState : function(b, ti, o) {
14038 			this.onSetProgressState.dispatch(this, b, ti, o);
14039 
14040 			return b;
14041 		},
14042 
14043 		load : function(o) {
14044 			var t = this, e = t.getElement(), h;
14045 
14046 			if (e) {
14047 				o = o || {};
14048 				o.load = true;
14049 
14050 				// Double encode existing entities in the value
14051 				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
14052 				o.element = e;
14053 
14054 				if (!o.no_events)
14055 					t.onLoadContent.dispatch(t, o);
14056 
14057 				o.element = e = null;
14058 
14059 				return h;
14060 			}
14061 		},
14062 
14063 		save : function(o) {
14064 			var t = this, e = t.getElement(), h, f;
14065 
14066 			if (!e || !t.initialized)
14067 				return;
14068 
14069 			o = o || {};
14070 			o.save = true;
14071 
14072 			o.element = e;
14073 			h = o.content = t.getContent(o);
14074 
14075 			if (!o.no_events)
14076 				t.onSaveContent.dispatch(t, o);
14077 
14078 			h = o.content;
14079 
14080 			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
14081 				e.innerHTML = h;
14082 
14083 				// Update hidden form element
14084 				if (f = DOM.getParent(t.id, 'form')) {
14085 					each(f.elements, function(e) {
14086 						if (e.name == t.id) {
14087 							e.value = h;
14088 							return false;
14089 						}
14090 					});
14091 				}
14092 			} else
14093 				e.value = h;
14094 
14095 			o.element = e = null;
14096 
14097 			return h;
14098 		},
14099 
14100 		setContent : function(content, args) {
14101 			var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
14102 
14103 			// Setup args object
14104 			args = args || {};
14105 			args.format = args.format || 'html';
14106 			args.set = true;
14107 			args.content = content;
14108 
14109 			// Do preprocessing
14110 			if (!args.no_events)
14111 				self.onBeforeSetContent.dispatch(self, args);
14112 
14113 			content = args.content;
14114 
14115 			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
14116 			// It will also be impossible to place the caret in the editor unless there is a BR element present
14117 			if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
14118 				forcedRootBlockName = self.settings.forced_root_block;
14119 				if (forcedRootBlockName)
14120 					content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
14121 				else
14122 					content = '<br data-mce-bogus="1">';
14123 
14124 				body.innerHTML = content;
14125 				self.selection.select(body, true);
14126 				self.selection.collapse(true);
14127 				return;
14128 			}
14129 
14130 			// Parse and serialize the html
14131 			if (args.format !== 'raw') {
14132 				content = new tinymce.html.Serializer({}, self.schema).serialize(
14133 					self.parser.parse(content)
14134 				);
14135 			}
14136 
14137 			// Set the new cleaned contents to the editor
14138 			args.content = tinymce.trim(content);
14139 			self.dom.setHTML(body, args.content);
14140 
14141 			// Do post processing
14142 			if (!args.no_events)
14143 				self.onSetContent.dispatch(self, args);
14144 
14145 			// Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
14146 			if (!self.settings.content_editable || document.activeElement === self.getBody()) {
14147 				self.selection.normalize();
14148 			}
14149 
14150 			return args.content;
14151 		},
14152 
14153 		getContent : function(args) {
14154 			var self = this, content;
14155 
14156 			// Setup args object
14157 			args = args || {};
14158 			args.format = args.format || 'html';
14159 			args.get = true;
14160 			args.getInner = true;
14161 
14162 			// Do preprocessing
14163 			if (!args.no_events)
14164 				self.onBeforeGetContent.dispatch(self, args);
14165 
14166 			// Get raw contents or by default the cleaned contents
14167 			if (args.format == 'raw')
14168 				content = self.getBody().innerHTML;
14169 			else
14170 				content = self.serializer.serialize(self.getBody(), args);
14171 
14172 			args.content = tinymce.trim(content);
14173 
14174 			// Do post processing
14175 			if (!args.no_events)
14176 				self.onGetContent.dispatch(self, args);
14177 
14178 			return args.content;
14179 		},
14180 
14181 		isDirty : function() {
14182 			var self = this;
14183 
14184 			return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
14185 		},
14186 
14187 		getContainer : function() {
14188 			var self = this;
14189 
14190 			if (!self.container)
14191 				self.container = DOM.get(self.editorContainer || self.id + '_parent');
14192 
14193 			return self.container;
14194 		},
14195 
14196 		getContentAreaContainer : function() {
14197 			return this.contentAreaContainer;
14198 		},
14199 
14200 		getElement : function() {
14201 			return DOM.get(this.settings.content_element || this.id);
14202 		},
14203 
14204 		getWin : function() {
14205 			var self = this, elm;
14206 
14207 			if (!self.contentWindow) {
14208 				elm = DOM.get(self.id + "_ifr");
14209 
14210 				if (elm)
14211 					self.contentWindow = elm.contentWindow;
14212 			}
14213 
14214 			return self.contentWindow;
14215 		},
14216 
14217 		getDoc : function() {
14218 			var self = this, win;
14219 
14220 			if (!self.contentDocument) {
14221 				win = self.getWin();
14222 
14223 				if (win)
14224 					self.contentDocument = win.document;
14225 			}
14226 
14227 			return self.contentDocument;
14228 		},
14229 
14230 		getBody : function() {
14231 			return this.bodyElement || this.getDoc().body;
14232 		},
14233 
14234 		convertURL : function(url, name, elm) {
14235 			var self = this, settings = self.settings;
14236 
14237 			// Use callback instead
14238 			if (settings.urlconverter_callback)
14239 				return self.execCallback('urlconverter_callback', url, elm, true, name);
14240 
14241 			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
14242 			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
14243 				return url;
14244 
14245 			// Convert to relative
14246 			if (settings.relative_urls)
14247 				return self.documentBaseURI.toRelative(url);
14248 
14249 			// Convert to absolute
14250 			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
14251 
14252 			return url;
14253 		},
14254 
14255 		addVisual : function(elm) {
14256 			var self = this, settings = self.settings, dom = self.dom, cls;
14257 
14258 			elm = elm || self.getBody();
14259 
14260 			if (!is(self.hasVisual))
14261 				self.hasVisual = settings.visual;
14262 
14263 			each(dom.select('table,a', elm), function(elm) {
14264 				var value;
14265 
14266 				switch (elm.nodeName) {
14267 					case 'TABLE':
14268 						cls = settings.visual_table_class || 'mceItemTable';
14269 						value = dom.getAttrib(elm, 'border');
14270 
14271 						if (!value || value == '0') {
14272 							if (self.hasVisual)
14273 								dom.addClass(elm, cls);
14274 							else
14275 								dom.removeClass(elm, cls);
14276 						}
14277 
14278 						return;
14279 
14280 					case 'A':
14281 						if (!dom.getAttrib(elm, 'href', false)) {
14282 							value = dom.getAttrib(elm, 'name') || elm.id;
14283 							cls = 'mceItemAnchor';
14284 
14285 							if (value) {
14286 								if (self.hasVisual)
14287 									dom.addClass(elm, cls);
14288 								else
14289 									dom.removeClass(elm, cls);
14290 							}
14291 						}
14292 
14293 						return;
14294 				}
14295 			});
14296 
14297 			self.onVisualAid.dispatch(self, elm, self.hasVisual);
14298 		},
14299 
14300 		remove : function() {
14301 			var self = this, elm = self.getContainer();
14302 
14303 			if (!self.removed) {
14304 				self.removed = 1; // Cancels post remove event execution
14305 				self.hide();
14306 
14307 				// Don't clear the window or document if content editable
14308 				// is enabled since other instances might still be present
14309 				if (!self.settings.content_editable) {
14310 					Event.unbind(self.getWin());
14311 					Event.unbind(self.getDoc());
14312 				}
14313 
14314 				Event.unbind(self.getBody());
14315 				Event.clear(elm);
14316 
14317 				self.execCallback('remove_instance_callback', self);
14318 				self.onRemove.dispatch(self);
14319 
14320 				// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
14321 				self.onExecCommand.listeners = [];
14322 
14323 				tinymce.remove(self);
14324 				DOM.remove(elm);
14325 			}
14326 		},
14327 
14328 		destroy : function(s) {
14329 			var t = this;
14330 
14331 			// One time is enough
14332 			if (t.destroyed)
14333 				return;
14334 
14335 			// We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
14336 			if (isGecko) {
14337 				Event.unbind(t.getDoc());
14338 				Event.unbind(t.getWin());
14339 				Event.unbind(t.getBody());
14340 			}
14341 
14342 			if (!s) {
14343 				tinymce.removeUnload(t.destroy);
14344 				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
14345 
14346 				// Manual destroy
14347 				if (t.theme && t.theme.destroy)
14348 					t.theme.destroy();
14349 
14350 				// Destroy controls, selection and dom
14351 				t.controlManager.destroy();
14352 				t.selection.destroy();
14353 				t.dom.destroy();
14354 			}
14355 
14356 			if (t.formElement) {
14357 				t.formElement.submit = t.formElement._mceOldSubmit;
14358 				t.formElement._mceOldSubmit = null;
14359 			}
14360 
14361 			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
14362 
14363 			if (t.selection)
14364 				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
14365 
14366 			t.destroyed = 1;
14367 		},
14368 
14369 		// Internal functions
14370 
14371 		_refreshContentEditable : function() {
14372 			var self = this, body, parent;
14373 
14374 			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
14375 			if (self._isHidden()) {
14376 				body = self.getBody();
14377 				parent = body.parentNode;
14378 
14379 				parent.removeChild(body);
14380 				parent.appendChild(body);
14381 
14382 				body.focus();
14383 			}
14384 		},
14385 
14386 		_isHidden : function() {
14387 			var s;
14388 
14389 			if (!isGecko)
14390 				return 0;
14391 
14392 			// Weird, wheres that cursor selection?
14393 			s = this.selection.getSel();
14394 			return (!s || !s.rangeCount || s.rangeCount === 0);
14395 		}
14396 	});
14397 })(tinymce);
14398 (function(tinymce) {
14399 	var each = tinymce.each;
14400 
14401 	tinymce.Editor.prototype.setupEvents = function() {
14402 		var self = this, settings = self.settings;
14403 
14404 		// Add events to the editor
14405 		each([
14406 			'onPreInit',
14407 
14408 			'onBeforeRenderUI',
14409 
14410 			'onPostRender',
14411 
14412 			'onLoad',
14413 
14414 			'onInit',
14415 
14416 			'onRemove',
14417 
14418 			'onActivate',
14419 
14420 			'onDeactivate',
14421 
14422 			'onClick',
14423 
14424 			'onEvent',
14425 
14426 			'onMouseUp',
14427 
14428 			'onMouseDown',
14429 
14430 			'onDblClick',
14431 
14432 			'onKeyDown',
14433 
14434 			'onKeyUp',
14435 
14436 			'onKeyPress',
14437 
14438 			'onContextMenu',
14439 
14440 			'onSubmit',
14441 
14442 			'onReset',
14443 
14444 			'onPaste',
14445 
14446 			'onPreProcess',
14447 
14448 			'onPostProcess',
14449 
14450 			'onBeforeSetContent',
14451 
14452 			'onBeforeGetContent',
14453 
14454 			'onSetContent',
14455 
14456 			'onGetContent',
14457 
14458 			'onLoadContent',
14459 
14460 			'onSaveContent',
14461 
14462 			'onNodeChange',
14463 
14464 			'onChange',
14465 
14466 			'onBeforeExecCommand',
14467 
14468 			'onExecCommand',
14469 
14470 			'onUndo',
14471 
14472 			'onRedo',
14473 
14474 			'onVisualAid',
14475 
14476 			'onSetProgressState',
14477 
14478 			'onSetAttrib'
14479 		], function(name) {
14480 			self[name] = new tinymce.util.Dispatcher(self);
14481 		});
14482 
14483 		// Handle legacy cleanup_callback option
14484 		if (settings.cleanup_callback) {
14485 			self.onBeforeSetContent.add(function(ed, o) {
14486 				o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14487 			});
14488 
14489 			self.onPreProcess.add(function(ed, o) {
14490 				if (o.set)
14491 					ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
14492 
14493 				if (o.get)
14494 					ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
14495 			});
14496 
14497 			self.onPostProcess.add(function(ed, o) {
14498 				if (o.set)
14499 					o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14500 
14501 				if (o.get)						
14502 					o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
14503 			});
14504 		}
14505 
14506 		// Handle legacy save_callback option
14507 		if (settings.save_callback) {
14508 			self.onGetContent.add(function(ed, o) {
14509 				if (o.save)
14510 					o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14511 			});
14512 		}
14513 
14514 		// Handle legacy handle_event_callback option
14515 		if (settings.handle_event_callback) {
14516 			self.onEvent.add(function(ed, e, o) {
14517 				if (self.execCallback('handle_event_callback', e, ed, o) === false) {
14518 					e.preventDefault();
14519 					e.stopPropagation();
14520 				}
14521 			});
14522 		}
14523 
14524 		// Handle legacy handle_node_change_callback option
14525 		if (settings.handle_node_change_callback) {
14526 			self.onNodeChange.add(function(ed, cm, n) {
14527 				ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
14528 			});
14529 		}
14530 
14531 		// Handle legacy save_callback option
14532 		if (settings.save_callback) {
14533 			self.onSaveContent.add(function(ed, o) {
14534 				var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14535 
14536 				if (h)
14537 					o.content = h;
14538 			});
14539 		}
14540 
14541 		// Handle legacy onchange_callback option
14542 		if (settings.onchange_callback) {
14543 			self.onChange.add(function(ed, l) {
14544 				ed.execCallback('onchange_callback', ed, l);
14545 			});
14546 		}
14547 	};
14548 
14549 	tinymce.Editor.prototype.bindNativeEvents = function() {
14550 		// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
14551 		var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
14552 
14553 		nativeToDispatcherMap = {
14554 			mouseup : 'onMouseUp',
14555 			mousedown : 'onMouseDown',
14556 			click : 'onClick',
14557 			keyup : 'onKeyUp',
14558 			keydown : 'onKeyDown',
14559 			keypress : 'onKeyPress',
14560 			submit : 'onSubmit',
14561 			reset : 'onReset',
14562 			contextmenu : 'onContextMenu',
14563 			dblclick : 'onDblClick',
14564 			paste : 'onPaste' // Doesn't work in all browsers yet
14565 		};
14566 
14567 		// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
14568 		function eventHandler(evt, args) {
14569 			var type = evt.type;
14570 
14571 			// Don't fire events when it's removed
14572 			if (self.removed)
14573 				return;
14574 
14575 			// Sends the native event out to a global dispatcher then to the specific event dispatcher
14576 			if (self.onEvent.dispatch(self, evt, args) !== false) {
14577 				self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
14578 			}
14579 		};
14580 
14581 		// Opera doesn't support focus event for contentEditable elements so we need to fake it
14582 		function doOperaFocus(e) {
14583 			self.focus(true);
14584 		};
14585 
14586 		function nodeChanged(ed, e) {
14587 			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
14588 			if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
14589 				self.selection.normalize();
14590 			}
14591 
14592 			self.nodeChanged();
14593 		}
14594 
14595 		// Add DOM events
14596 		each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
14597 			var root = settings.content_editable ? self.getBody() : self.getDoc();
14598 
14599 			switch (nativeName) {
14600 				case 'contextmenu':
14601 					dom.bind(root, nativeName, eventHandler);
14602 					break;
14603 
14604 				case 'paste':
14605 					dom.bind(self.getBody(), nativeName, eventHandler);
14606 					break;
14607 
14608 				case 'submit':
14609 				case 'reset':
14610 					dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
14611 					break;
14612 
14613 				default:
14614 					dom.bind(root, nativeName, eventHandler);
14615 			}
14616 		});
14617 
14618 		// Set the editor as active when focused
14619 		dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
14620 			self.focus(true);
14621 		});
14622 
14623 		if (settings.content_editable && tinymce.isOpera) {
14624 			dom.bind(self.getBody(), 'click', doOperaFocus);
14625 			dom.bind(self.getBody(), 'keydown', doOperaFocus);
14626 		}
14627 
14628 		// Add node change handler
14629 		self.onMouseUp.add(nodeChanged);
14630 
14631 		self.onKeyUp.add(function(ed, e) {
14632 			var keyCode = e.keyCode;
14633 
14634 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
14635 				nodeChanged(ed, e);
14636 		});
14637 
14638 		// Add reset handler
14639 		self.onReset.add(function() {
14640 			self.setContent(self.startContent, {format : 'raw'});
14641 		});
14642 
14643 		// Add shortcuts
14644 		function handleShortcut(e, execute) {
14645 			if (e.altKey || e.ctrlKey || e.metaKey) {
14646 				each(self.shortcuts, function(shortcut) {
14647 					var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
14648 
14649 					if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
14650 						return;
14651 
14652 					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
14653 						e.preventDefault();
14654 
14655 						if (execute) {
14656 							shortcut.func.call(shortcut.scope);
14657 						}
14658 
14659 						return true;
14660 					}
14661 				});
14662 			}
14663 		};
14664 
14665 		self.onKeyUp.add(function(ed, e) {
14666 			handleShortcut(e);
14667 		});
14668 
14669 		self.onKeyPress.add(function(ed, e) {
14670 			handleShortcut(e);
14671 		});
14672 
14673 		self.onKeyDown.add(function(ed, e) {
14674 			handleShortcut(e, true);
14675 		});
14676 
14677 		if (tinymce.isOpera) {
14678 			self.onClick.add(function(ed, e) {
14679 				e.preventDefault();
14680 			});
14681 		}
14682 	};
14683 })(tinymce);
14684 (function(tinymce) {
14685 	// Added for compression purposes
14686 	var each = tinymce.each, undef, TRUE = true, FALSE = false;
14687 
14688 	tinymce.EditorCommands = function(editor) {
14689 		var dom = editor.dom,
14690 			selection = editor.selection,
14691 			commands = {state: {}, exec : {}, value : {}},
14692 			settings = editor.settings,
14693 			formatter = editor.formatter,
14694 			bookmark;
14695 
14696 		function execCommand(command, ui, value) {
14697 			var func;
14698 
14699 			command = command.toLowerCase();
14700 			if (func = commands.exec[command]) {
14701 				func(command, ui, value);
14702 				return TRUE;
14703 			}
14704 
14705 			return FALSE;
14706 		};
14707 
14708 		function queryCommandState(command) {
14709 			var func;
14710 
14711 			command = command.toLowerCase();
14712 			if (func = commands.state[command])
14713 				return func(command);
14714 
14715 			return -1;
14716 		};
14717 
14718 		function queryCommandValue(command) {
14719 			var func;
14720 
14721 			command = command.toLowerCase();
14722 			if (func = commands.value[command])
14723 				return func(command);
14724 
14725 			return FALSE;
14726 		};
14727 
14728 		function addCommands(command_list, type) {
14729 			type = type || 'exec';
14730 
14731 			each(command_list, function(callback, command) {
14732 				each(command.toLowerCase().split(','), function(command) {
14733 					commands[type][command] = callback;
14734 				});
14735 			});
14736 		};
14737 
14738 		// Expose public methods
14739 		tinymce.extend(this, {
14740 			execCommand : execCommand,
14741 			queryCommandState : queryCommandState,
14742 			queryCommandValue : queryCommandValue,
14743 			addCommands : addCommands
14744 		});
14745 
14746 		// Private methods
14747 
14748 		function execNativeCommand(command, ui, value) {
14749 			if (ui === undef)
14750 				ui = FALSE;
14751 
14752 			if (value === undef)
14753 				value = null;
14754 
14755 			return editor.getDoc().execCommand(command, ui, value);
14756 		};
14757 
14758 		function isFormatMatch(name) {
14759 			return formatter.match(name);
14760 		};
14761 
14762 		function toggleFormat(name, value) {
14763 			formatter.toggle(name, value ? {value : value} : undef);
14764 		};
14765 
14766 		function storeSelection(type) {
14767 			bookmark = selection.getBookmark(type);
14768 		};
14769 
14770 		function restoreSelection() {
14771 			selection.moveToBookmark(bookmark);
14772 		};
14773 
14774 		// Add execCommand overrides
14775 		addCommands({
14776 			// Ignore these, added for compatibility
14777 			'mceResetDesignMode,mceBeginUndoLevel' : function() {},
14778 
14779 			// Add undo manager logic
14780 			'mceEndUndoLevel,mceAddUndoLevel' : function() {
14781 				editor.undoManager.add();
14782 			},
14783 
14784 			'Cut,Copy,Paste' : function(command) {
14785 				var doc = editor.getDoc(), failed;
14786 
14787 				// Try executing the native command
14788 				try {
14789 					execNativeCommand(command);
14790 				} catch (ex) {
14791 					// Command failed
14792 					failed = TRUE;
14793 				}
14794 
14795 				// Present alert message about clipboard access not being available
14796 				if (failed || !doc.queryCommandSupported(command)) {
14797 					if (tinymce.isGecko) {
14798 						editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
14799 							if (state)
14800 								open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
14801 						});
14802 					} else
14803 						editor.windowManager.alert(editor.getLang('clipboard_no_support'));
14804 				}
14805 			},
14806 
14807 			// Override unlink command
14808 			unlink : function(command) {
14809 				if (selection.isCollapsed())
14810 					selection.select(selection.getNode());
14811 
14812 				execNativeCommand(command);
14813 				selection.collapse(FALSE);
14814 			},
14815 
14816 			// Override justify commands to use the text formatter engine
14817 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
14818 				var align = command.substring(7);
14819 
14820 				// Remove all other alignments first
14821 				each('left,center,right,full'.split(','), function(name) {
14822 					if (align != name)
14823 						formatter.remove('align' + name);
14824 				});
14825 
14826 				toggleFormat('align' + align);
14827 				execCommand('mceRepaint');
14828 			},
14829 
14830 			// Override list commands to fix WebKit bug
14831 			'InsertUnorderedList,InsertOrderedList' : function(command) {
14832 				var listElm, listParent;
14833 
14834 				execNativeCommand(command);
14835 
14836 				// WebKit produces lists within block elements so we need to split them
14837 				// we will replace the native list creation logic to custom logic later on
14838 				// TODO: Remove this when the list creation logic is removed
14839 				listElm = dom.getParent(selection.getNode(), 'ol,ul');
14840 				if (listElm) {
14841 					listParent = listElm.parentNode;
14842 
14843 					// If list is within a text block then split that block
14844 					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
14845 						storeSelection();
14846 						dom.split(listParent, listElm);
14847 						restoreSelection();
14848 					}
14849 				}
14850 			},
14851 
14852 			// Override commands to use the text formatter engine
14853 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
14854 				toggleFormat(command);
14855 			},
14856 
14857 			// Override commands to use the text formatter engine
14858 			'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
14859 				toggleFormat(command, value);
14860 			},
14861 
14862 			FontSize : function(command, ui, value) {
14863 				var fontClasses, fontSizes;
14864 
14865 				// Convert font size 1-7 to styles
14866 				if (value >= 1 && value <= 7) {
14867 					fontSizes = tinymce.explode(settings.font_size_style_values);
14868 					fontClasses = tinymce.explode(settings.font_size_classes);
14869 
14870 					if (fontClasses)
14871 						value = fontClasses[value - 1] || value;
14872 					else
14873 						value = fontSizes[value - 1] || value;
14874 				}
14875 
14876 				toggleFormat(command, value);
14877 			},
14878 
14879 			RemoveFormat : function(command) {
14880 				formatter.remove(command);
14881 			},
14882 
14883 			mceBlockQuote : function(command) {
14884 				toggleFormat('blockquote');
14885 			},
14886 
14887 			FormatBlock : function(command, ui, value) {
14888 				return toggleFormat(value || 'p');
14889 			},
14890 
14891 			mceCleanup : function() {
14892 				var bookmark = selection.getBookmark();
14893 
14894 				editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
14895 
14896 				selection.moveToBookmark(bookmark);
14897 			},
14898 
14899 			mceRemoveNode : function(command, ui, value) {
14900 				var node = value || selection.getNode();
14901 
14902 				// Make sure that the body node isn't removed
14903 				if (node != editor.getBody()) {
14904 					storeSelection();
14905 					editor.dom.remove(node, TRUE);
14906 					restoreSelection();
14907 				}
14908 			},
14909 
14910 			mceSelectNodeDepth : function(command, ui, value) {
14911 				var counter = 0;
14912 
14913 				dom.getParent(selection.getNode(), function(node) {
14914 					if (node.nodeType == 1 && counter++ == value) {
14915 						selection.select(node);
14916 						return FALSE;
14917 					}
14918 				}, editor.getBody());
14919 			},
14920 
14921 			mceSelectNode : function(command, ui, value) {
14922 				selection.select(value);
14923 			},
14924 
14925 			mceInsertContent : function(command, ui, value) {
14926 				var parser, serializer, parentNode, rootNode, fragment, args,
14927 					marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
14928 
14929 				//selection.normalize();
14930 
14931 				// Setup parser and serializer
14932 				parser = editor.parser;
14933 				serializer = new tinymce.html.Serializer({}, editor.schema);
14934 				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
14935 
14936 				// Run beforeSetContent handlers on the HTML to be inserted
14937 				args = {content: value, format: 'html'};
14938 				selection.onBeforeSetContent.dispatch(selection, args);
14939 				value = args.content;
14940 
14941 				// Add caret at end of contents if it's missing
14942 				if (value.indexOf('{$caret}') == -1)
14943 					value += '{$caret}';
14944 
14945 				// Replace the caret marker with a span bookmark element
14946 				value = value.replace(/\{\$caret\}/, bookmarkHtml);
14947 
14948 				// Insert node maker where we will insert the new HTML and get it's parent
14949 				if (!selection.isCollapsed())
14950 					editor.getDoc().execCommand('Delete', false, null);
14951 
14952 				parentNode = selection.getNode();
14953 
14954 				// Parse the fragment within the context of the parent node
14955 				args = {context : parentNode.nodeName.toLowerCase()};
14956 				fragment = parser.parse(value, args);
14957 
14958 				// Move the caret to a more suitable location
14959 				node = fragment.lastChild;
14960 				if (node.attr('id') == 'mce_marker') {
14961 					marker = node;
14962 
14963 					for (node = node.prev; node; node = node.walk(true)) {
14964 						if (node.type == 3 || !dom.isBlock(node.name)) {
14965 							node.parent.insert(marker, node, node.name === 'br');
14966 							break;
14967 						}
14968 					}
14969 				}
14970 
14971 				// If parser says valid we can insert the contents into that parent
14972 				if (!args.invalid) {
14973 					value = serializer.serialize(fragment);
14974 
14975 					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
14976 					node = parentNode.firstChild;
14977 					node2 = parentNode.lastChild;
14978 					if (!node || (node === node2 && node.nodeName === 'BR'))
14979 						dom.setHTML(parentNode, value);
14980 					else
14981 						selection.setContent(value);
14982 				} else {
14983 					// If the fragment was invalid within that context then we need
14984 					// to parse and process the parent it's inserted into
14985 
14986 					// Insert bookmark node and get the parent
14987 					selection.setContent(bookmarkHtml);
14988 					parentNode = editor.selection.getNode();
14989 					rootNode = editor.getBody();
14990 
14991 					// Opera will return the document node when selection is in root
14992 					if (parentNode.nodeType == 9)
14993 						parentNode = node = rootNode;
14994 					else
14995 						node = parentNode;
14996 
14997 					// Find the ancestor just before the root element
14998 					while (node !== rootNode) {
14999 						parentNode = node;
15000 						node = node.parentNode;
15001 					}
15002 
15003 					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
15004 					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
15005 					value = serializer.serialize(
15006 						parser.parse(
15007 							// Need to replace by using a function since $ in the contents would otherwise be a problem
15008 							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
15009 								return serializer.serialize(fragment);
15010 							})
15011 						)
15012 					);
15013 
15014 					// Set the inner/outer HTML depending on if we are in the root or not
15015 					if (parentNode == rootNode)
15016 						dom.setHTML(rootNode, value);
15017 					else
15018 						dom.setOuterHTML(parentNode, value);
15019 				}
15020 
15021 				marker = dom.get('mce_marker');
15022 
15023 				// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
15024 				nodeRect = dom.getRect(marker);
15025 				viewPortRect = dom.getViewPort(editor.getWin());
15026 
15027 				// Check if node is out side the viewport if it is then scroll to it
15028 				if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
15029 					(nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
15030 					viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
15031 					viewportBodyElement.scrollLeft = nodeRect.x;
15032 					viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
15033 				}
15034 
15035 				// Move selection before marker and remove it
15036 				rng = dom.createRng();
15037 
15038 				// If previous sibling is a text node set the selection to the end of that node
15039 				node = marker.previousSibling;
15040 				if (node && node.nodeType == 3) {
15041 					rng.setStart(node, node.nodeValue.length);
15042 				} else {
15043 					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
15044 					rng.setStartBefore(marker);
15045 					rng.setEndBefore(marker);
15046 				}
15047 
15048 				// Remove the marker node and set the new range
15049 				dom.remove(marker);
15050 				selection.setRng(rng);
15051 
15052 				// Dispatch after event and add any visual elements needed
15053 				selection.onSetContent.dispatch(selection, args);
15054 				editor.addVisual();
15055 			},
15056 
15057 			mceInsertRawHTML : function(command, ui, value) {
15058 				selection.setContent('tiny_mce_marker');
15059 				editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
15060 			},
15061 
15062 			mceToggleFormat : function(command, ui, value) {
15063 				toggleFormat(value);
15064 			},
15065 
15066 			mceSetContent : function(command, ui, value) {
15067 				editor.setContent(value);
15068 			},
15069 
15070 			'Indent,Outdent' : function(command) {
15071 				var intentValue, indentUnit, value;
15072 
15073 				// Setup indent level
15074 				intentValue = settings.indentation;
15075 				indentUnit = /[a-z%]+$/i.exec(intentValue);
15076 				intentValue = parseInt(intentValue);
15077 
15078 				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
15079 					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
15080 					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
15081 						formatter.apply('div');
15082 					}
15083 
15084 					each(selection.getSelectedBlocks(), function(element) {
15085 						if (command == 'outdent') {
15086 							value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
15087 							dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
15088 						} else
15089 							dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
15090 					});
15091 				} else
15092 					execNativeCommand(command);
15093 			},
15094 
15095 			mceRepaint : function() {
15096 				var bookmark;
15097 
15098 				if (tinymce.isGecko) {
15099 					try {
15100 						storeSelection(TRUE);
15101 
15102 						if (selection.getSel())
15103 							selection.getSel().selectAllChildren(editor.getBody());
15104 
15105 						selection.collapse(TRUE);
15106 						restoreSelection();
15107 					} catch (ex) {
15108 						// Ignore
15109 					}
15110 				}
15111 			},
15112 
15113 			mceToggleFormat : function(command, ui, value) {
15114 				formatter.toggle(value);
15115 			},
15116 
15117 			InsertHorizontalRule : function() {
15118 				editor.execCommand('mceInsertContent', false, '<hr />');
15119 			},
15120 
15121 			mceToggleVisualAid : function() {
15122 				editor.hasVisual = !editor.hasVisual;
15123 				editor.addVisual();
15124 			},
15125 
15126 			mceReplaceContent : function(command, ui, value) {
15127 				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
15128 			},
15129 
15130 			mceInsertLink : function(command, ui, value) {
15131 				var anchor;
15132 
15133 				if (typeof(value) == 'string')
15134 					value = {href : value};
15135 
15136 				anchor = dom.getParent(selection.getNode(), 'a');
15137 
15138 				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
15139 				value.href = value.href.replace(' ', '%20');
15140 
15141 				// Remove existing links if there could be child links or that the href isn't specified
15142 				if (!anchor || !value.href) {
15143 					formatter.remove('link');
15144 				}		
15145 
15146 				// Apply new link to selection
15147 				if (value.href) {
15148 					formatter.apply('link', value, anchor);
15149 				}
15150 			},
15151 
15152 			selectAll : function() {
15153 				var root = dom.getRoot(), rng = dom.createRng();
15154 
15155 				rng.setStart(root, 0);
15156 				rng.setEnd(root, root.childNodes.length);
15157 
15158 				editor.selection.setRng(rng);
15159 			}
15160 		});
15161 
15162 		// Add queryCommandState overrides
15163 		addCommands({
15164 			// Override justify commands
15165 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15166 				var name = 'align' + command.substring(7);
15167 				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
15168 				var matches = tinymce.map(nodes, function(node) {
15169 					return !!formatter.matchNode(node, name);
15170 				});
15171 				return tinymce.inArray(matches, TRUE) !== -1;
15172 			},
15173 
15174 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15175 				return isFormatMatch(command);
15176 			},
15177 
15178 			mceBlockQuote : function() {
15179 				return isFormatMatch('blockquote');
15180 			},
15181 
15182 			Outdent : function() {
15183 				var node;
15184 
15185 				if (settings.inline_styles) {
15186 					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15187 						return TRUE;
15188 
15189 					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15190 						return TRUE;
15191 				}
15192 
15193 				return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
15194 			},
15195 
15196 			'InsertUnorderedList,InsertOrderedList' : function(command) {
15197 				return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
15198 			}
15199 		}, 'state');
15200 
15201 		// Add queryCommandValue overrides
15202 		addCommands({
15203 			'FontSize,FontName' : function(command) {
15204 				var value = 0, parent;
15205 
15206 				if (parent = dom.getParent(selection.getNode(), 'span')) {
15207 					if (command == 'fontsize')
15208 						value = parent.style.fontSize;
15209 					else
15210 						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
15211 				}
15212 
15213 				return value;
15214 			}
15215 		}, 'value');
15216 
15217 		// Add undo manager logic
15218 		addCommands({
15219 			Undo : function() {
15220 				editor.undoManager.undo();
15221 			},
15222 
15223 			Redo : function() {
15224 				editor.undoManager.redo();
15225 			}
15226 		});
15227 	};
15228 })(tinymce);
15229 
15230 (function(tinymce) {
15231 	var Dispatcher = tinymce.util.Dispatcher;
15232 
15233 	tinymce.UndoManager = function(editor) {
15234 		var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
15235 
15236 		function getContent() {
15237 			// Remove whitespace before/after and remove pure bogus nodes
15238 			return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
15239 		};
15240 
15241 		function addNonTypingUndoLevel() {
15242 			self.typing = false;
15243 			self.add();
15244 		};
15245 
15246 		// Create event instances
15247 		onBeforeAdd = new Dispatcher(self);
15248 		onAdd       = new Dispatcher(self);
15249 		onUndo      = new Dispatcher(self);
15250 		onRedo      = new Dispatcher(self);
15251 
15252 		// Pass though onAdd event from UndoManager to Editor as onChange
15253 		onAdd.add(function(undoman, level) {
15254 			if (undoman.hasUndo())
15255 				return editor.onChange.dispatch(editor, level, undoman);
15256 		});
15257 
15258 		// Pass though onUndo event from UndoManager to Editor
15259 		onUndo.add(function(undoman, level) {
15260 			return editor.onUndo.dispatch(editor, level, undoman);
15261 		});
15262 
15263 		// Pass though onRedo event from UndoManager to Editor
15264 		onRedo.add(function(undoman, level) {
15265 			return editor.onRedo.dispatch(editor, level, undoman);
15266 		});
15267 
15268 		// Add initial undo level when the editor is initialized
15269 		editor.onInit.add(function() {
15270 			self.add();
15271 		});
15272 
15273 		// Get position before an execCommand is processed
15274 		editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
15275 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15276 				self.beforeChange();
15277 			}
15278 		});
15279 
15280 		// Add undo level after an execCommand call was made
15281 		editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
15282 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15283 				self.add();
15284 			}
15285 		});
15286 
15287 		// Add undo level on save contents, drag end and blur/focusout
15288 		editor.onSaveContent.add(addNonTypingUndoLevel);
15289 		editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
15290 		editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) {
15291 			if (!editor.removed && self.typing) {
15292 				addNonTypingUndoLevel();
15293 			}
15294 		});
15295 
15296 		editor.onKeyUp.add(function(editor, e) {
15297 			var keyCode = e.keyCode;
15298 
15299 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
15300 				addNonTypingUndoLevel();
15301 			}
15302 		});
15303 
15304 		editor.onKeyDown.add(function(editor, e) {
15305 			var keyCode = e.keyCode;
15306 
15307 			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
15308 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
15309 				if (self.typing) {
15310 					addNonTypingUndoLevel();
15311 				}
15312 
15313 				return;
15314 			}
15315 
15316 			// If key isn't shift,ctrl,alt,capslock,metakey
15317 			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
15318 				self.beforeChange();
15319 				self.typing = true;
15320 				self.add();
15321 			}
15322 		});
15323 
15324 		editor.onMouseDown.add(function(editor, e) {
15325 			if (self.typing) {
15326 				addNonTypingUndoLevel();
15327 			}
15328 		});
15329 
15330 		// Add keyboard shortcuts for undo/redo keys
15331 		editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
15332 		editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
15333 
15334 		self = {
15335 			// Explose for debugging reasons
15336 			data : data,
15337 
15338 			typing : false,
15339 			
15340 			onBeforeAdd: onBeforeAdd,
15341 
15342 			onAdd : onAdd,
15343 
15344 			onUndo : onUndo,
15345 
15346 			onRedo : onRedo,
15347 
15348 			beforeChange : function() {
15349 				beforeBookmark = editor.selection.getBookmark(2, true);
15350 			},
15351 
15352 			add : function(level) {
15353 				var i, settings = editor.settings, lastLevel;
15354 
15355 				level = level || {};
15356 				level.content = getContent();
15357 				
15358 				self.onBeforeAdd.dispatch(self, level);
15359 
15360 				// Add undo level if needed
15361 				lastLevel = data[index];
15362 				if (lastLevel && lastLevel.content == level.content)
15363 					return null;
15364 
15365 				// Set before bookmark on previous level
15366 				if (data[index])
15367 					data[index].beforeBookmark = beforeBookmark;
15368 
15369 				// Time to compress
15370 				if (settings.custom_undo_redo_levels) {
15371 					if (data.length > settings.custom_undo_redo_levels) {
15372 						for (i = 0; i < data.length - 1; i++)
15373 							data[i] = data[i + 1];
15374 
15375 						data.length--;
15376 						index = data.length;
15377 					}
15378 				}
15379 
15380 				// Get a non intrusive normalized bookmark
15381 				level.bookmark = editor.selection.getBookmark(2, true);
15382 
15383 				// Crop array if needed
15384 				if (index < data.length - 1)
15385 					data.length = index + 1;
15386 
15387 				data.push(level);
15388 				index = data.length - 1;
15389 
15390 				self.onAdd.dispatch(self, level);
15391 				editor.isNotDirty = 0;
15392 
15393 				return level;
15394 			},
15395 
15396 			undo : function() {
15397 				var level, i;
15398 
15399 				if (self.typing) {
15400 					self.add();
15401 					self.typing = false;
15402 				}
15403 
15404 				if (index > 0) {
15405 					level = data[--index];
15406 
15407 					editor.setContent(level.content, {format : 'raw'});
15408 					editor.selection.moveToBookmark(level.beforeBookmark);
15409 
15410 					self.onUndo.dispatch(self, level);
15411 				}
15412 
15413 				return level;
15414 			},
15415 
15416 			redo : function() {
15417 				var level;
15418 
15419 				if (index < data.length - 1) {
15420 					level = data[++index];
15421 
15422 					editor.setContent(level.content, {format : 'raw'});
15423 					editor.selection.moveToBookmark(level.bookmark);
15424 
15425 					self.onRedo.dispatch(self, level);
15426 				}
15427 
15428 				return level;
15429 			},
15430 
15431 			clear : function() {
15432 				data = [];
15433 				index = 0;
15434 				self.typing = false;
15435 			},
15436 
15437 			hasUndo : function() {
15438 				return index > 0 || this.typing;
15439 			},
15440 
15441 			hasRedo : function() {
15442 				return index < data.length - 1 && !this.typing;
15443 			}
15444 		};
15445 
15446 		return self;
15447 	};
15448 })(tinymce);
15449 
15450 tinymce.ForceBlocks = function(editor) {
15451 	var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
15452 
15453 	function addRootBlocks() {
15454 		var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
15455 
15456 		if (!node || node.nodeType !== 1 || !settings.forced_root_block)
15457 			return;
15458 
15459 		// Check if node is wrapped in block
15460 		while (node && node != rootNode) {
15461 			if (blockElements[node.nodeName])
15462 				return;
15463 
15464 			node = node.parentNode;
15465 		}
15466 
15467 		// Get current selection
15468 		rng = selection.getRng();
15469 		if (rng.setStart) {
15470 			startContainer = rng.startContainer;
15471 			startOffset = rng.startOffset;
15472 			endContainer = rng.endContainer;
15473 			endOffset = rng.endOffset;
15474 		} else {
15475 			// Force control range into text range
15476 			if (rng.item) {
15477 				node = rng.item(0);
15478 				rng = editor.getDoc().body.createTextRange();
15479 				rng.moveToElementText(node);
15480 			}
15481 
15482 			isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
15483 			tmpRng = rng.duplicate();
15484 			tmpRng.collapse(true);
15485 			startOffset = tmpRng.move('character', offset) * -1;
15486 
15487 			if (!tmpRng.collapsed) {
15488 				tmpRng = rng.duplicate();
15489 				tmpRng.collapse(false);
15490 				endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
15491 			}
15492 		}
15493 
15494 		// Wrap non block elements and text nodes
15495 		node = rootNode.firstChild;
15496 		while (node) {
15497 			if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
15498 				if (!rootBlockNode) {
15499 					rootBlockNode = dom.create(settings.forced_root_block);
15500 					node.parentNode.insertBefore(rootBlockNode, node);
15501 					wrapped = true;
15502 				}
15503 
15504 				tempNode = node;
15505 				node = node.nextSibling;
15506 				rootBlockNode.appendChild(tempNode);
15507 			} else {
15508 				rootBlockNode = null;
15509 				node = node.nextSibling;
15510 			}
15511 		}
15512 
15513 		if (wrapped) {
15514 			if (rng.setStart) {
15515 				rng.setStart(startContainer, startOffset);
15516 				rng.setEnd(endContainer, endOffset);
15517 				selection.setRng(rng);
15518 			} else {
15519 				// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
15520 				if (isInEditorDocument) {
15521 					try {
15522 						rng = editor.getDoc().body.createTextRange();
15523 						rng.moveToElementText(rootNode);
15524 						rng.collapse(true);
15525 						rng.moveStart('character', startOffset);
15526 
15527 						if (endOffset > 0)
15528 							rng.moveEnd('character', endOffset);
15529 
15530 						rng.select();
15531 					} catch (ex) {
15532 						// Ignore
15533 					}
15534 				}
15535 			}
15536 
15537 			editor.nodeChanged();
15538 		}
15539 	};
15540 
15541 	// Force root blocks
15542 	if (settings.forced_root_block) {
15543 		editor.onKeyUp.add(addRootBlocks);
15544 		editor.onNodeChange.add(addRootBlocks);
15545 	}
15546 };
15547 
15548 (function(tinymce) {
15549 	// Shorten names
15550 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
15551 
15552 	tinymce.create('tinymce.ControlManager', {
15553 		ControlManager : function(ed, s) {
15554 			var t = this, i;
15555 
15556 			s = s || {};
15557 			t.editor = ed;
15558 			t.controls = {};
15559 			t.onAdd = new tinymce.util.Dispatcher(t);
15560 			t.onPostRender = new tinymce.util.Dispatcher(t);
15561 			t.prefix = s.prefix || ed.id + '_';
15562 			t._cls = {};
15563 
15564 			t.onPostRender.add(function() {
15565 				each(t.controls, function(c) {
15566 					c.postRender();
15567 				});
15568 			});
15569 		},
15570 
15571 		get : function(id) {
15572 			return this.controls[this.prefix + id] || this.controls[id];
15573 		},
15574 
15575 		setActive : function(id, s) {
15576 			var c = null;
15577 
15578 			if (c = this.get(id))
15579 				c.setActive(s);
15580 
15581 			return c;
15582 		},
15583 
15584 		setDisabled : function(id, s) {
15585 			var c = null;
15586 
15587 			if (c = this.get(id))
15588 				c.setDisabled(s);
15589 
15590 			return c;
15591 		},
15592 
15593 		add : function(c) {
15594 			var t = this;
15595 
15596 			if (c) {
15597 				t.controls[c.id] = c;
15598 				t.onAdd.dispatch(c, t);
15599 			}
15600 
15601 			return c;
15602 		},
15603 
15604 		createControl : function(name) {
15605 			var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
15606 
15607 			// Build control factory cache
15608 			if (!self.controlFactories) {
15609 				self.controlFactories = [];
15610 				each(editor.plugins, function(plugin) {
15611 					if (plugin.createControl) {
15612 						self.controlFactories.push(plugin);
15613 					}
15614 				});
15615 			}
15616 
15617 			// Create controls by asking cached factories
15618 			factories = self.controlFactories;
15619 			for (i = 0, l = factories.length; i < l; i++) {
15620 				ctrl = factories[i].createControl(name, self);
15621 
15622 				if (ctrl) {
15623 					return self.add(ctrl);
15624 				}
15625 			}
15626 
15627 			// Create sepearator
15628 			if (name === "|" || name === "separator") {
15629 				return self.createSeparator();
15630 			}
15631 
15632 			// Create control from button collection
15633 			if (editor.buttons && (ctrl = editor.buttons[name])) {
15634 				return self.createButton(name, ctrl);
15635 			}
15636 
15637 			return self.add(ctrl);
15638 		},
15639 
15640 		createDropMenu : function(id, s, cc) {
15641 			var t = this, ed = t.editor, c, bm, v, cls;
15642 
15643 			s = extend({
15644 				'class' : 'mceDropDown',
15645 				constrain : ed.settings.constrain_menus
15646 			}, s);
15647 
15648 			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
15649 			if (v = ed.getParam('skin_variant'))
15650 				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
15651 
15652 			s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
15653 
15654 			id = t.prefix + id;
15655 			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
15656 			c = t.controls[id] = new cls(id, s);
15657 			c.onAddItem.add(function(c, o) {
15658 				var s = o.settings;
15659 
15660 				s.title = ed.getLang(s.title, s.title);
15661 
15662 				if (!s.onclick) {
15663 					s.onclick = function(v) {
15664 						if (s.cmd)
15665 							ed.execCommand(s.cmd, s.ui || false, s.value);
15666 					};
15667 				}
15668 			});
15669 
15670 			ed.onRemove.add(function() {
15671 				c.destroy();
15672 			});
15673 
15674 			// Fix for bug #1897785, #1898007
15675 			if (tinymce.isIE) {
15676 				c.onShowMenu.add(function() {
15677 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15678 					ed.focus();
15679 
15680 					bm = ed.selection.getBookmark(1);
15681 				});
15682 
15683 				c.onHideMenu.add(function() {
15684 					if (bm) {
15685 						ed.selection.moveToBookmark(bm);
15686 						bm = 0;
15687 					}
15688 				});
15689 			}
15690 
15691 			return t.add(c);
15692 		},
15693 
15694 		createListBox : function(id, s, cc) {
15695 			var t = this, ed = t.editor, cmd, c, cls;
15696 
15697 			if (t.get(id))
15698 				return null;
15699 
15700 			s.title = ed.translate(s.title);
15701 			s.scope = s.scope || ed;
15702 
15703 			if (!s.onselect) {
15704 				s.onselect = function(v) {
15705 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15706 				};
15707 			}
15708 
15709 			s = extend({
15710 				title : s.title,
15711 				'class' : 'mce_' + id,
15712 				scope : s.scope,
15713 				control_manager : t
15714 			}, s);
15715 
15716 			id = t.prefix + id;
15717 
15718 
15719 			function useNativeListForAccessibility(ed) {
15720 				return ed.settings.use_accessible_selects && !tinymce.isGecko
15721 			}
15722 
15723 			if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
15724 				c = new tinymce.ui.NativeListBox(id, s);
15725 			else {
15726 				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
15727 				c = new cls(id, s, ed);
15728 			}
15729 
15730 			t.controls[id] = c;
15731 
15732 			// Fix focus problem in Safari
15733 			if (tinymce.isWebKit) {
15734 				c.onPostRender.add(function(c, n) {
15735 					// Store bookmark on mousedown
15736 					Event.add(n, 'mousedown', function() {
15737 						ed.bookmark = ed.selection.getBookmark(1);
15738 					});
15739 
15740 					// Restore on focus, since it might be lost
15741 					Event.add(n, 'focus', function() {
15742 						ed.selection.moveToBookmark(ed.bookmark);
15743 						ed.bookmark = null;
15744 					});
15745 				});
15746 			}
15747 
15748 			if (c.hideMenu)
15749 				ed.onMouseDown.add(c.hideMenu, c);
15750 
15751 			return t.add(c);
15752 		},
15753 
15754 		createButton : function(id, s, cc) {
15755 			var t = this, ed = t.editor, o, c, cls;
15756 
15757 			if (t.get(id))
15758 				return null;
15759 
15760 			s.title = ed.translate(s.title);
15761 			s.label = ed.translate(s.label);
15762 			s.scope = s.scope || ed;
15763 
15764 			if (!s.onclick && !s.menu_button) {
15765 				s.onclick = function() {
15766 					ed.execCommand(s.cmd, s.ui || false, s.value);
15767 				};
15768 			}
15769 
15770 			s = extend({
15771 				title : s.title,
15772 				'class' : 'mce_' + id,
15773 				unavailable_prefix : ed.getLang('unavailable', ''),
15774 				scope : s.scope,
15775 				control_manager : t
15776 			}, s);
15777 
15778 			id = t.prefix + id;
15779 
15780 			if (s.menu_button) {
15781 				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
15782 				c = new cls(id, s, ed);
15783 				ed.onMouseDown.add(c.hideMenu, c);
15784 			} else {
15785 				cls = t._cls.button || tinymce.ui.Button;
15786 				c = new cls(id, s, ed);
15787 			}
15788 
15789 			return t.add(c);
15790 		},
15791 
15792 		createMenuButton : function(id, s, cc) {
15793 			s = s || {};
15794 			s.menu_button = 1;
15795 
15796 			return this.createButton(id, s, cc);
15797 		},
15798 
15799 		createSplitButton : function(id, s, cc) {
15800 			var t = this, ed = t.editor, cmd, c, cls;
15801 
15802 			if (t.get(id))
15803 				return null;
15804 
15805 			s.title = ed.translate(s.title);
15806 			s.scope = s.scope || ed;
15807 
15808 			if (!s.onclick) {
15809 				s.onclick = function(v) {
15810 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15811 				};
15812 			}
15813 
15814 			if (!s.onselect) {
15815 				s.onselect = function(v) {
15816 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15817 				};
15818 			}
15819 
15820 			s = extend({
15821 				title : s.title,
15822 				'class' : 'mce_' + id,
15823 				scope : s.scope,
15824 				control_manager : t
15825 			}, s);
15826 
15827 			id = t.prefix + id;
15828 			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
15829 			c = t.add(new cls(id, s, ed));
15830 			ed.onMouseDown.add(c.hideMenu, c);
15831 
15832 			return c;
15833 		},
15834 
15835 		createColorSplitButton : function(id, s, cc) {
15836 			var t = this, ed = t.editor, cmd, c, cls, bm;
15837 
15838 			if (t.get(id))
15839 				return null;
15840 
15841 			s.title = ed.translate(s.title);
15842 			s.scope = s.scope || ed;
15843 
15844 			if (!s.onclick) {
15845 				s.onclick = function(v) {
15846 					if (tinymce.isIE)
15847 						bm = ed.selection.getBookmark(1);
15848 
15849 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15850 				};
15851 			}
15852 
15853 			if (!s.onselect) {
15854 				s.onselect = function(v) {
15855 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15856 				};
15857 			}
15858 
15859 			s = extend({
15860 				title : s.title,
15861 				'class' : 'mce_' + id,
15862 				'menu_class' : ed.getParam('skin') + 'Skin',
15863 				scope : s.scope,
15864 				more_colors_title : ed.getLang('more_colors')
15865 			}, s);
15866 
15867 			id = t.prefix + id;
15868 			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
15869 			c = new cls(id, s, ed);
15870 			ed.onMouseDown.add(c.hideMenu, c);
15871 
15872 			// Remove the menu element when the editor is removed
15873 			ed.onRemove.add(function() {
15874 				c.destroy();
15875 			});
15876 
15877 			// Fix for bug #1897785, #1898007
15878 			if (tinymce.isIE) {
15879 				c.onShowMenu.add(function() {
15880 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15881 					ed.focus();
15882 					bm = ed.selection.getBookmark(1);
15883 				});
15884 
15885 				c.onHideMenu.add(function() {
15886 					if (bm) {
15887 						ed.selection.moveToBookmark(bm);
15888 						bm = 0;
15889 					}
15890 				});
15891 			}
15892 
15893 			return t.add(c);
15894 		},
15895 
15896 		createToolbar : function(id, s, cc) {
15897 			var c, t = this, cls;
15898 
15899 			id = t.prefix + id;
15900 			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
15901 			c = new cls(id, s, t.editor);
15902 
15903 			if (t.get(id))
15904 				return null;
15905 
15906 			return t.add(c);
15907 		},
15908 		
15909 		createToolbarGroup : function(id, s, cc) {
15910 			var c, t = this, cls;
15911 			id = t.prefix + id;
15912 			cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
15913 			c = new cls(id, s, t.editor);
15914 			
15915 			if (t.get(id))
15916 				return null;
15917 			
15918 			return t.add(c);
15919 		},
15920 
15921 		createSeparator : function(cc) {
15922 			var cls = cc || this._cls.separator || tinymce.ui.Separator;
15923 
15924 			return new cls();
15925 		},
15926 
15927 		setControlType : function(n, c) {
15928 			return this._cls[n.toLowerCase()] = c;
15929 		},
15930 	
15931 		destroy : function() {
15932 			each(this.controls, function(c) {
15933 				c.destroy();
15934 			});
15935 
15936 			this.controls = null;
15937 		}
15938 	});
15939 })(tinymce);
15940 
15941 (function(tinymce) {
15942 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
15943 
15944 	tinymce.create('tinymce.WindowManager', {
15945 		WindowManager : function(ed) {
15946 			var t = this;
15947 
15948 			t.editor = ed;
15949 			t.onOpen = new Dispatcher(t);
15950 			t.onClose = new Dispatcher(t);
15951 			t.params = {};
15952 			t.features = {};
15953 		},
15954 
15955 		open : function(s, p) {
15956 			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
15957 
15958 			// Default some options
15959 			s = s || {};
15960 			p = p || {};
15961 			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
15962 			sh = isOpera ? vp.h : screen.height;
15963 			s.name = s.name || 'mc_' + new Date().getTime();
15964 			s.width = parseInt(s.width || 320);
15965 			s.height = parseInt(s.height || 240);
15966 			s.resizable = true;
15967 			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
15968 			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
15969 			p.inline = false;
15970 			p.mce_width = s.width;
15971 			p.mce_height = s.height;
15972 			p.mce_auto_focus = s.auto_focus;
15973 
15974 			if (mo) {
15975 				if (isIE) {
15976 					s.center = true;
15977 					s.help = false;
15978 					s.dialogWidth = s.width + 'px';
15979 					s.dialogHeight = s.height + 'px';
15980 					s.scroll = s.scrollbars || false;
15981 				}
15982 			}
15983 
15984 			// Build features string
15985 			each(s, function(v, k) {
15986 				if (tinymce.is(v, 'boolean'))
15987 					v = v ? 'yes' : 'no';
15988 
15989 				if (!/^(name|url)$/.test(k)) {
15990 					if (isIE && mo)
15991 						f += (f ? ';' : '') + k + ':' + v;
15992 					else
15993 						f += (f ? ',' : '') + k + '=' + v;
15994 				}
15995 			});
15996 
15997 			t.features = s;
15998 			t.params = p;
15999 			t.onOpen.dispatch(t, s, p);
16000 
16001 			u = s.url || s.file;
16002 			u = tinymce._addVer(u);
16003 
16004 			try {
16005 				if (isIE && mo) {
16006 					w = 1;
16007 					window.showModalDialog(u, window, f);
16008 				} else
16009 					w = window.open(u, s.name, f);
16010 			} catch (ex) {
16011 				// Ignore
16012 			}
16013 
16014 			if (!w)
16015 				alert(t.editor.getLang('popup_blocked'));
16016 		},
16017 
16018 		close : function(w) {
16019 			w.close();
16020 			this.onClose.dispatch(this);
16021 		},
16022 
16023 		createInstance : function(cl, a, b, c, d, e) {
16024 			var f = tinymce.resolve(cl);
16025 
16026 			return new f(a, b, c, d, e);
16027 		},
16028 
16029 		confirm : function(t, cb, s, w) {
16030 			w = w || window;
16031 
16032 			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
16033 		},
16034 
16035 		alert : function(tx, cb, s, w) {
16036 			var t = this;
16037 
16038 			w = w || window;
16039 			w.alert(t._decode(t.editor.getLang(tx, tx)));
16040 
16041 			if (cb)
16042 				cb.call(s || t);
16043 		},
16044 
16045 		resizeBy : function(dw, dh, win) {
16046 			win.resizeBy(dw, dh);
16047 		},
16048 
16049 		// Internal functions
16050 
16051 		_decode : function(s) {
16052 			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
16053 		}
16054 	});
16055 }(tinymce));
16056 (function(tinymce) {
16057 	tinymce.Formatter = function(ed) {
16058 		var formats = {},
16059 			each = tinymce.each,
16060 			dom = ed.dom,
16061 			selection = ed.selection,
16062 			TreeWalker = tinymce.dom.TreeWalker,
16063 			rangeUtils = new tinymce.dom.RangeUtils(dom),
16064 			isValid = ed.schema.isValidChild,
16065 			isBlock = dom.isBlock,
16066 			forcedRootBlock = ed.settings.forced_root_block,
16067 			nodeIndex = dom.nodeIndex,
16068 			INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
16069 			MCE_ATTR_RE = /^(src|href|style)$/,
16070 			FALSE = false,
16071 			TRUE = true,
16072 			formatChangeData,
16073 			undef,
16074 			getContentEditable = dom.getContentEditable;
16075 
16076 		function isArray(obj) {
16077 			return obj instanceof Array;
16078 		};
16079 
16080 		function getParents(node, selector) {
16081 			return dom.getParents(node, selector, dom.getRoot());
16082 		};
16083 
16084 		function isCaretNode(node) {
16085 			return node.nodeType === 1 && node.id === '_mce_caret';
16086 		};
16087 
16088 		function defaultFormats() {
16089 			register({
16090 				alignleft : [
16091 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
16092 					{selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
16093 				],
16094 
16095 				aligncenter : [
16096 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
16097 					{selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
16098 					{selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
16099 				],
16100 
16101 				alignright : [
16102 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
16103 					{selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
16104 				],
16105 
16106 				alignfull : [
16107 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
16108 				],
16109 
16110 				bold : [
16111 					{inline : 'strong', remove : 'all'},
16112 					{inline : 'span', styles : {fontWeight : 'bold'}},
16113 					{inline : 'b', remove : 'all'}
16114 				],
16115 
16116 				italic : [
16117 					{inline : 'em', remove : 'all'},
16118 					{inline : 'span', styles : {fontStyle : 'italic'}},
16119 					{inline : 'i', remove : 'all'}
16120 				],
16121 
16122 				underline : [
16123 					{inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
16124 					{inline : 'u', remove : 'all'}
16125 				],
16126 
16127 				strikethrough : [
16128 					{inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
16129 					{inline : 'strike', remove : 'all'}
16130 				],
16131 
16132 				forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
16133 				hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
16134 				fontname : {inline : 'span', styles : {fontFamily : '%value'}},
16135 				fontsize : {inline : 'span', styles : {fontSize : '%value'}},
16136 				fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
16137 				blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
16138 				subscript : {inline : 'sub'},
16139 				superscript : {inline : 'sup'},
16140 
16141 				link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
16142 					onmatch : function(node) {
16143 						return true;
16144 					},
16145 
16146 					onformat : function(elm, fmt, vars) {
16147 						each(vars, function(value, key) {
16148 							dom.setAttrib(elm, key, value);
16149 						});
16150 					}
16151 				},
16152 
16153 				removeformat : [
16154 					{selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
16155 					{selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
16156 					{selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
16157 				]
16158 			});
16159 
16160 			// Register default block formats
16161 			each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
16162 				register(name, {block : name, remove : 'all'});
16163 			});
16164 
16165 			// Register user defined formats
16166 			register(ed.settings.formats);
16167 		};
16168 
16169 		function addKeyboardShortcuts() {
16170 			// Add some inline shortcuts
16171 			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
16172 			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
16173 			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
16174 
16175 			// BlockFormat shortcuts keys
16176 			for (var i = 1; i <= 6; i++) {
16177 				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
16178 			}
16179 
16180 			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
16181 			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
16182 			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
16183 		};
16184 
16185 		// Public functions
16186 
16187 		function get(name) {
16188 			return name ? formats[name] : formats;
16189 		};
16190 
16191 		function register(name, format) {
16192 			if (name) {
16193 				if (typeof(name) !== 'string') {
16194 					each(name, function(format, name) {
16195 						register(name, format);
16196 					});
16197 				} else {
16198 					// Force format into array and add it to internal collection
16199 					format = format.length ? format : [format];
16200 
16201 					each(format, function(format) {
16202 						// Set deep to false by default on selector formats this to avoid removing
16203 						// alignment on images inside paragraphs when alignment is changed on paragraphs
16204 						if (format.deep === undef)
16205 							format.deep = !format.selector;
16206 
16207 						// Default to true
16208 						if (format.split === undef)
16209 							format.split = !format.selector || format.inline;
16210 
16211 						// Default to true
16212 						if (format.remove === undef && format.selector && !format.inline)
16213 							format.remove = 'none';
16214 
16215 						// Mark format as a mixed format inline + block level
16216 						if (format.selector && format.inline) {
16217 							format.mixed = true;
16218 							format.block_expand = true;
16219 						}
16220 
16221 						// Split classes if needed
16222 						if (typeof(format.classes) === 'string')
16223 							format.classes = format.classes.split(/\s+/);
16224 					});
16225 
16226 					formats[name] = format;
16227 				}
16228 			}
16229 		};
16230 
16231 		var getTextDecoration = function(node) {
16232 			var decoration;
16233 
16234 			ed.dom.getParent(node, function(n) {
16235 				decoration = ed.dom.getStyle(n, 'text-decoration');
16236 				return decoration && decoration !== 'none';
16237 			});
16238 
16239 			return decoration;
16240 		};
16241 
16242 		var processUnderlineAndColor = function(node) {
16243 			var textDecoration;
16244 			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
16245 				textDecoration = getTextDecoration(node.parentNode);
16246 				if (ed.dom.getStyle(node, 'color') && textDecoration) {
16247 					ed.dom.setStyle(node, 'text-decoration', textDecoration);
16248 				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
16249 					ed.dom.setStyle(node, 'text-decoration', null);
16250 				}
16251 			}
16252 		};
16253 
16254 		function apply(name, vars, node) {
16255 			var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
16256 
16257 			function setElementFormat(elm, fmt) {
16258 				fmt = fmt || format;
16259 
16260 				if (elm) {
16261 					if (fmt.onformat) {
16262 						fmt.onformat(elm, fmt, vars, node);
16263 					}
16264 
16265 					each(fmt.styles, function(value, name) {
16266 						dom.setStyle(elm, name, replaceVars(value, vars));
16267 					});
16268 
16269 					each(fmt.attributes, function(value, name) {
16270 						dom.setAttrib(elm, name, replaceVars(value, vars));
16271 					});
16272 
16273 					each(fmt.classes, function(value) {
16274 						value = replaceVars(value, vars);
16275 
16276 						if (!dom.hasClass(elm, value))
16277 							dom.addClass(elm, value);
16278 					});
16279 				}
16280 			};
16281 			function adjustSelectionToVisibleSelection() {
16282 				function findSelectionEnd(start, end) {
16283 					var walker = new TreeWalker(end);
16284 					for (node = walker.current(); node; node = walker.prev()) {
16285 						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
16286 							return node;
16287 						}
16288 					}
16289 				};
16290 
16291 				// Adjust selection so that a end container with a end offset of zero is not included in the selection
16292 				// as this isn't visible to the user.
16293 				var rng = ed.selection.getRng();
16294 				var start = rng.startContainer;
16295 				var end = rng.endContainer;
16296 
16297 				if (start != end && rng.endOffset === 0) {
16298 					var newEnd = findSelectionEnd(start, end);
16299 					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
16300 
16301 					rng.setEnd(newEnd, endOffset);
16302 				}
16303 
16304 				return rng;
16305 			}
16306 			
16307 			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
16308 				var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
16309 				
16310 				// find the index of the first child list.
16311 				each(node.childNodes, function(n, index) {
16312 					if (n.nodeName === "UL" || n.nodeName === "OL") {
16313 						listIndex = index;
16314 						list = n;
16315 						return false;
16316 					}
16317 				});
16318 				
16319 				// get the index of the bookmarks
16320 				each(node.childNodes, function(n, index) {
16321 					if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
16322 						if (n.id == bookmark.id + "_start") {
16323 							startIndex = index;
16324 						} else if (n.id == bookmark.id + "_end") {
16325 							endIndex = index;
16326 						}
16327 					}
16328 				});
16329 				
16330 				// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
16331 				if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
16332 					each(tinymce.grep(node.childNodes), process);
16333 					return 0;
16334 				} else {
16335 					currentWrapElm = dom.clone(wrapElm, FALSE);
16336 
16337 					// create a list of the nodes on the same side of the list as the selection
16338 					each(tinymce.grep(node.childNodes), function(n, index) {
16339 						if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
16340 							nodes.push(n); 
16341 							n.parentNode.removeChild(n);
16342 						}
16343 					});
16344 
16345 					// insert the wrapping element either before or after the list.
16346 					if (startIndex < listIndex) {
16347 						node.insertBefore(currentWrapElm, list);
16348 					} else if (startIndex > listIndex) {
16349 						node.insertBefore(currentWrapElm, list.nextSibling);
16350 					}
16351 					
16352 					// add the new nodes to the list.
16353 					newWrappers.push(currentWrapElm);
16354 
16355 					each(nodes, function(node) {
16356 						currentWrapElm.appendChild(node);
16357 					});
16358 
16359 					return currentWrapElm;
16360 				}
16361 			};
16362 
16363 			function applyRngStyle(rng, bookmark, node_specific) {
16364 				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
16365 
16366 				// Setup wrapper element
16367 				wrapName = format.inline || format.block;
16368 				wrapElm = dom.create(wrapName);
16369 				setElementFormat(wrapElm);
16370 
16371 				rangeUtils.walk(rng, function(nodes) {
16372 					var currentWrapElm;
16373 
16374 					function process(node) {
16375 						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
16376 
16377 						lastContentEditable = contentEditable;
16378 						nodeName = node.nodeName.toLowerCase();
16379 						parentName = node.parentNode.nodeName.toLowerCase();
16380 
16381 						// Node has a contentEditable value
16382 						if (node.nodeType === 1 && getContentEditable(node)) {
16383 							lastContentEditable = contentEditable;
16384 							contentEditable = getContentEditable(node) === "true";
16385 							hasContentEditableState = true; // We don't want to wrap the container only it's children
16386 						}
16387 
16388 						// Stop wrapping on br elements
16389 						if (isEq(nodeName, 'br')) {
16390 							currentWrapElm = 0;
16391 
16392 							// Remove any br elements when we wrap things
16393 							if (format.block)
16394 								dom.remove(node);
16395 
16396 							return;
16397 						}
16398 
16399 						// If node is wrapper type
16400 						if (format.wrapper && matchNode(node, name, vars)) {
16401 							currentWrapElm = 0;
16402 							return;
16403 						}
16404 
16405 						// Can we rename the block
16406 						if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
16407 							node = dom.rename(node, wrapName);
16408 							setElementFormat(node);
16409 							newWrappers.push(node);
16410 							currentWrapElm = 0;
16411 							return;
16412 						}
16413 
16414 						// Handle selector patterns
16415 						if (format.selector) {
16416 							// Look for matching formats
16417 							each(formatList, function(format) {
16418 								// Check collapsed state if it exists
16419 								if ('collapsed' in format && format.collapsed !== isCollapsed) {
16420 									return;
16421 								}
16422 
16423 								if (dom.is(node, format.selector) && !isCaretNode(node)) {
16424 									setElementFormat(node, format);
16425 									found = true;
16426 								}
16427 							});
16428 
16429 							// Continue processing if a selector match wasn't found and a inline element is defined
16430 							if (!format.inline || found) {
16431 								currentWrapElm = 0;
16432 								return;
16433 							}
16434 						}
16435 
16436 						// Is it valid to wrap this item
16437 						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
16438 								!(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
16439 							// Start wrapping
16440 							if (!currentWrapElm) {
16441 								// Wrap the node
16442 								currentWrapElm = dom.clone(wrapElm, FALSE);
16443 								node.parentNode.insertBefore(currentWrapElm, node);
16444 								newWrappers.push(currentWrapElm);
16445 							}
16446 
16447 							currentWrapElm.appendChild(node);
16448 						} else if (nodeName == 'li' && bookmark) {
16449 							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
16450 							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
16451 						} else {
16452 							// Start a new wrapper for possible children
16453 							currentWrapElm = 0;
16454 							
16455 							each(tinymce.grep(node.childNodes), process);
16456 
16457 							if (hasContentEditableState) {
16458 								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16459 							}
16460 
16461 							// End the last wrapper
16462 							currentWrapElm = 0;
16463 						}
16464 					};
16465 
16466 					// Process siblings from range
16467 					each(nodes, process);
16468 				});
16469 
16470 				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
16471 				if (format.wrap_links === false) {
16472 					each(newWrappers, function(node) {
16473 						function process(node) {
16474 							var i, currentWrapElm, children;
16475 
16476 							if (node.nodeName === 'A') {
16477 								currentWrapElm = dom.clone(wrapElm, FALSE);
16478 								newWrappers.push(currentWrapElm);
16479 
16480 								children = tinymce.grep(node.childNodes);
16481 								for (i = 0; i < children.length; i++)
16482 									currentWrapElm.appendChild(children[i]);
16483 
16484 								node.appendChild(currentWrapElm);
16485 							}
16486 
16487 							each(tinymce.grep(node.childNodes), process);
16488 						};
16489 
16490 						process(node);
16491 					});
16492 				}
16493 
16494 				// Cleanup
16495 				
16496 				each(newWrappers, function(node) {
16497 					var childCount;
16498 
16499 					function getChildCount(node) {
16500 						var count = 0;
16501 
16502 						each(node.childNodes, function(node) {
16503 							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
16504 								count++;
16505 						});
16506 
16507 						return count;
16508 					};
16509 
16510 					function mergeStyles(node) {
16511 						var child, clone;
16512 
16513 						each(node.childNodes, function(node) {
16514 							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
16515 								child = node;
16516 								return FALSE; // break loop
16517 							}
16518 						});
16519 
16520 						// If child was found and of the same type as the current node
16521 						if (child && matchName(child, format)) {
16522 							clone = dom.clone(child, FALSE);
16523 							setElementFormat(clone);
16524 
16525 							dom.replace(clone, node, TRUE);
16526 							dom.remove(child, 1);
16527 						}
16528 
16529 						return clone || node;
16530 					};
16531 
16532 					childCount = getChildCount(node);
16533 
16534 					// Remove empty nodes but only if there is multiple wrappers and they are not block
16535 					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
16536 					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
16537 						dom.remove(node, 1);
16538 						return;
16539 					}
16540 
16541 					if (format.inline || format.wrapper) {
16542 						// Merges the current node with it's children of similar type to reduce the number of elements
16543 						if (!format.exact && childCount === 1)
16544 							node = mergeStyles(node);
16545 
16546 						// Remove/merge children
16547 						each(formatList, function(format) {
16548 							// Merge all children of similar type will move styles from child to parent
16549 							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
16550 							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
16551 							each(dom.select(format.inline, node), function(child) {
16552 								var parent;
16553 
16554 								// When wrap_links is set to false we don't want
16555 								// to remove the format on children within links
16556 								if (format.wrap_links === false) {
16557 									parent = child.parentNode;
16558 
16559 									do {
16560 										if (parent.nodeName === 'A')
16561 											return;
16562 									} while (parent = parent.parentNode);
16563 								}
16564 
16565 								removeFormat(format, vars, child, format.exact ? child : null);
16566 							});
16567 						});
16568 
16569 						// Remove child if direct parent is of same type
16570 						if (matchNode(node.parentNode, name, vars)) {
16571 							dom.remove(node, 1);
16572 							node = 0;
16573 							return TRUE;
16574 						}
16575 
16576 						// Look for parent with similar style format
16577 						if (format.merge_with_parents) {
16578 							dom.getParent(node.parentNode, function(parent) {
16579 								if (matchNode(parent, name, vars)) {
16580 									dom.remove(node, 1);
16581 									node = 0;
16582 									return TRUE;
16583 								}
16584 							});
16585 						}
16586 
16587 						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
16588 						if (node && format.merge_siblings !== false) {
16589 							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
16590 							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
16591 						}
16592 					}
16593 				});
16594 			};
16595 
16596 			if (format) {
16597 				if (node) {
16598 					if (node.nodeType) {
16599 						rng = dom.createRng();
16600 						rng.setStartBefore(node);
16601 						rng.setEndAfter(node);
16602 						applyRngStyle(expandRng(rng, formatList), null, true);
16603 					} else {
16604 						applyRngStyle(node, null, true);
16605 					}
16606 				} else {
16607 					if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16608 						// Obtain selection node before selection is unselected by applyRngStyle()
16609 						var curSelNode = ed.selection.getNode();
16610 
16611 						// If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
16612 						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
16613 						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
16614 							apply(formatList[0].defaultBlock);
16615 						}
16616 
16617 						// Apply formatting to selection
16618 						ed.selection.setRng(adjustSelectionToVisibleSelection());
16619 						bookmark = selection.getBookmark();
16620 						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
16621 
16622 						// Colored nodes should be underlined so that the color of the underline matches the text color.
16623 						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
16624 							tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
16625 							processUnderlineAndColor(curSelNode);
16626 						}
16627 
16628 						selection.moveToBookmark(bookmark);
16629 						moveStart(selection.getRng(TRUE));
16630 						ed.nodeChanged();
16631 					} else
16632 						performCaretAction('apply', name, vars);
16633 				}
16634 			}
16635 		};
16636 
16637 		function remove(name, vars, node) {
16638 			var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
16639 
16640 			// Merges the styles for each node
16641 			function process(node) {
16642 				var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
16643 
16644 				// Node has a contentEditable value
16645 				if (node.nodeType === 1 && getContentEditable(node)) {
16646 					lastContentEditable = contentEditable;
16647 					contentEditable = getContentEditable(node) === "true";
16648 					hasContentEditableState = true; // We don't want to wrap the container only it's children
16649 				}
16650 
16651 				// Grab the children first since the nodelist might be changed
16652 				children = tinymce.grep(node.childNodes);
16653 
16654 				// Process current node
16655 				if (contentEditable && !hasContentEditableState) {
16656 					for (i = 0, l = formatList.length; i < l; i++) {
16657 						if (removeFormat(formatList[i], vars, node, node))
16658 							break;
16659 					}
16660 				}
16661 
16662 				// Process the children
16663 				if (format.deep) {
16664 					if (children.length) {					
16665 						for (i = 0, l = children.length; i < l; i++)
16666 							process(children[i]);
16667 
16668 						if (hasContentEditableState) {
16669 							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16670 						}
16671 					}
16672 				}
16673 			};
16674 
16675 			function findFormatRoot(container) {
16676 				var formatRoot;
16677 
16678 				// Find format root
16679 				each(getParents(container.parentNode).reverse(), function(parent) {
16680 					var format;
16681 
16682 					// Find format root element
16683 					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
16684 						// Is the node matching the format we are looking for
16685 						format = matchNode(parent, name, vars);
16686 						if (format && format.split !== false)
16687 							formatRoot = parent;
16688 					}
16689 				});
16690 
16691 				return formatRoot;
16692 			};
16693 
16694 			function wrapAndSplit(format_root, container, target, split) {
16695 				var parent, clone, lastClone, firstClone, i, formatRootParent;
16696 
16697 				// Format root found then clone formats and split it
16698 				if (format_root) {
16699 					formatRootParent = format_root.parentNode;
16700 
16701 					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
16702 						clone = dom.clone(parent, FALSE);
16703 
16704 						for (i = 0; i < formatList.length; i++) {
16705 							if (removeFormat(formatList[i], vars, clone, clone)) {
16706 								clone = 0;
16707 								break;
16708 							}
16709 						}
16710 
16711 						// Build wrapper node
16712 						if (clone) {
16713 							if (lastClone)
16714 								clone.appendChild(lastClone);
16715 
16716 							if (!firstClone)
16717 								firstClone = clone;
16718 
16719 							lastClone = clone;
16720 						}
16721 					}
16722 
16723 					// Never split block elements if the format is mixed
16724 					if (split && (!format.mixed || !isBlock(format_root)))
16725 						container = dom.split(format_root, container);
16726 
16727 					// Wrap container in cloned formats
16728 					if (lastClone) {
16729 						target.parentNode.insertBefore(lastClone, target);
16730 						firstClone.appendChild(target);
16731 					}
16732 				}
16733 
16734 				return container;
16735 			};
16736 
16737 			function splitToFormatRoot(container) {
16738 				return wrapAndSplit(findFormatRoot(container), container, container, true);
16739 			};
16740 
16741 			function unwrap(start) {
16742 				var node = dom.get(start ? '_start' : '_end'),
16743 					out = node[start ? 'firstChild' : 'lastChild'];
16744 
16745 				// If the end is placed within the start the result will be removed
16746 				// So this checks if the out node is a bookmark node if it is it
16747 				// checks for another more suitable node
16748 				if (isBookmarkNode(out))
16749 					out = out[start ? 'firstChild' : 'lastChild'];
16750 
16751 				dom.remove(node, true);
16752 
16753 				return out;
16754 			};
16755 
16756 			function removeRngStyle(rng) {
16757 				var startContainer, endContainer, node;
16758 
16759 				rng = expandRng(rng, formatList, TRUE);
16760 
16761 				if (format.split) {
16762 					startContainer = getContainer(rng, TRUE);
16763 					endContainer = getContainer(rng);
16764 
16765 					if (startContainer != endContainer) {
16766 						// WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
16767 						// This will happen if you tripple click a table cell and use remove formatting
16768 						if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
16769 							startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
16770 						}
16771 
16772 						// Wrap start/end nodes in span element since these might be cloned/moved
16773 						startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
16774 						endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
16775 
16776 						// Split start/end
16777 						splitToFormatRoot(startContainer);
16778 						splitToFormatRoot(endContainer);
16779 
16780 						// Unwrap start/end to get real elements again
16781 						startContainer = unwrap(TRUE);
16782 						endContainer = unwrap();
16783 					} else
16784 						startContainer = endContainer = splitToFormatRoot(startContainer);
16785 
16786 					// Update range positions since they might have changed after the split operations
16787 					rng.startContainer = startContainer.parentNode;
16788 					rng.startOffset = nodeIndex(startContainer);
16789 					rng.endContainer = endContainer.parentNode;
16790 					rng.endOffset = nodeIndex(endContainer) + 1;
16791 				}
16792 
16793 				// Remove items between start/end
16794 				rangeUtils.walk(rng, function(nodes) {
16795 					each(nodes, function(node) {
16796 						process(node);
16797 
16798 						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
16799 						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
16800 							removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
16801 						}
16802 					});
16803 				});
16804 			};
16805 
16806 			// Handle node
16807 			if (node) {
16808 				if (node.nodeType) {
16809 					rng = dom.createRng();
16810 					rng.setStartBefore(node);
16811 					rng.setEndAfter(node);
16812 					removeRngStyle(rng);
16813 				} else {
16814 					removeRngStyle(node);
16815 				}
16816 
16817 				return;
16818 			}
16819 
16820 			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16821 				bookmark = selection.getBookmark();
16822 				removeRngStyle(selection.getRng(TRUE));
16823 				selection.moveToBookmark(bookmark);
16824 
16825 				// Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
16826 				if (format.inline && match(name, vars, selection.getStart())) {
16827 					moveStart(selection.getRng(true));
16828 				}
16829 
16830 				ed.nodeChanged();
16831 			} else
16832 				performCaretAction('remove', name, vars);
16833 		};
16834 
16835 		function toggle(name, vars, node) {
16836 			var fmt = get(name);
16837 
16838 			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
16839 				remove(name, vars, node);
16840 			else
16841 				apply(name, vars, node);
16842 		};
16843 
16844 		function matchNode(node, name, vars, similar) {
16845 			var formatList = get(name), format, i, classes;
16846 
16847 			function matchItems(node, format, item_name) {
16848 				var key, value, items = format[item_name], i;
16849 
16850 				// Custom match
16851 				if (format.onmatch) {
16852 					return format.onmatch(node, format, item_name);
16853 				}
16854 
16855 				// Check all items
16856 				if (items) {
16857 					// Non indexed object
16858 					if (items.length === undef) {
16859 						for (key in items) {
16860 							if (items.hasOwnProperty(key)) {
16861 								if (item_name === 'attributes')
16862 									value = dom.getAttrib(node, key);
16863 								else
16864 									value = getStyle(node, key);
16865 
16866 								if (similar && !value && !format.exact)
16867 									return;
16868 
16869 								if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
16870 									return;
16871 							}
16872 						}
16873 					} else {
16874 						// Only one match needed for indexed arrays
16875 						for (i = 0; i < items.length; i++) {
16876 							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
16877 								return format;
16878 						}
16879 					}
16880 				}
16881 
16882 				return format;
16883 			};
16884 
16885 			if (formatList && node) {
16886 				// Check each format in list
16887 				for (i = 0; i < formatList.length; i++) {
16888 					format = formatList[i];
16889 
16890 					// Name name, attributes, styles and classes
16891 					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
16892 						// Match classes
16893 						if (classes = format.classes) {
16894 							for (i = 0; i < classes.length; i++) {
16895 								if (!dom.hasClass(node, classes[i]))
16896 									return;
16897 							}
16898 						}
16899 
16900 						return format;
16901 					}
16902 				}
16903 			}
16904 		};
16905 
16906 		function match(name, vars, node) {
16907 			var startNode;
16908 
16909 			function matchParents(node) {
16910 				// Find first node with similar format settings
16911 				node = dom.getParent(node, function(node) {
16912 					return !!matchNode(node, name, vars, true);
16913 				});
16914 
16915 				// Do an exact check on the similar format element
16916 				return matchNode(node, name, vars);
16917 			};
16918 
16919 			// Check specified node
16920 			if (node)
16921 				return matchParents(node);
16922 
16923 			// Check selected node
16924 			node = selection.getNode();
16925 			if (matchParents(node))
16926 				return TRUE;
16927 
16928 			// Check start node if it's different
16929 			startNode = selection.getStart();
16930 			if (startNode != node) {
16931 				if (matchParents(startNode))
16932 					return TRUE;
16933 			}
16934 
16935 			return FALSE;
16936 		};
16937 
16938 		function matchAll(names, vars) {
16939 			var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
16940 
16941 			// Check start of selection for formats
16942 			startElement = selection.getStart();
16943 			dom.getParent(startElement, function(node) {
16944 				var i, name;
16945 
16946 				for (i = 0; i < names.length; i++) {
16947 					name = names[i];
16948 
16949 					if (!checkedMap[name] && matchNode(node, name, vars)) {
16950 						checkedMap[name] = true;
16951 						matchedFormatNames.push(name);
16952 					}
16953 				}
16954 			}, dom.getRoot());
16955 
16956 			return matchedFormatNames;
16957 		};
16958 
16959 		function canApply(name) {
16960 			var formatList = get(name), startNode, parents, i, x, selector;
16961 
16962 			if (formatList) {
16963 				startNode = selection.getStart();
16964 				parents = getParents(startNode);
16965 
16966 				for (x = formatList.length - 1; x >= 0; x--) {
16967 					selector = formatList[x].selector;
16968 
16969 					// Format is not selector based, then always return TRUE
16970 					if (!selector)
16971 						return TRUE;
16972 
16973 					for (i = parents.length - 1; i >= 0; i--) {
16974 						if (dom.is(parents[i], selector))
16975 							return TRUE;
16976 					}
16977 				}
16978 			}
16979 
16980 			return FALSE;
16981 		};
16982 
16983 		function formatChanged(formats, callback) {
16984 			var currentFormats;
16985 
16986 			// Setup format node change logic
16987 			if (!formatChangeData) {
16988 				formatChangeData = {};
16989 				currentFormats = {};
16990 
16991 				ed.onNodeChange.addToTop(function(ed, cm, node) {
16992 					var parents = getParents(node), matchedFormats = {};
16993 
16994 					// Check for new formats
16995 					each(formatChangeData, function(callbacks, format) {
16996 						each(parents, function(node) {
16997 							if (matchNode(node, format, {}, true)) {
16998 								if (!currentFormats[format]) {
16999 									// Execute callbacks
17000 									each(callbacks, function(callback) {
17001 										callback(true, {node: node, format: format, parents: parents});
17002 									});
17003 
17004 									currentFormats[format] = callbacks;
17005 								}
17006 
17007 								matchedFormats[format] = callbacks;
17008 								return false;
17009 							}
17010 						});
17011 					});
17012 
17013 					// Check if current formats still match
17014 					each(currentFormats, function(callbacks, format) {
17015 						if (!matchedFormats[format]) {
17016 							delete currentFormats[format];
17017 
17018 							each(callbacks, function(callback) {
17019 								callback(false, {node: node, format: format, parents: parents});
17020 							});
17021 						}
17022 					});
17023 				});
17024 			}
17025 
17026 			// Add format listeners
17027 			each(formats.split(','), function(format) {
17028 				if (!formatChangeData[format]) {
17029 					formatChangeData[format] = [];
17030 				}
17031 
17032 				formatChangeData[format].push(callback);
17033 			});
17034 
17035 			return this;
17036 		};
17037 
17038 		// Expose to public
17039 		tinymce.extend(this, {
17040 			get : get,
17041 			register : register,
17042 			apply : apply,
17043 			remove : remove,
17044 			toggle : toggle,
17045 			match : match,
17046 			matchAll : matchAll,
17047 			matchNode : matchNode,
17048 			canApply : canApply,
17049 			formatChanged: formatChanged
17050 		});
17051 
17052 		// Initialize
17053 		defaultFormats();
17054 		addKeyboardShortcuts();
17055 
17056 		// Private functions
17057 
17058 		function matchName(node, format) {
17059 			// Check for inline match
17060 			if (isEq(node, format.inline))
17061 				return TRUE;
17062 
17063 			// Check for block match
17064 			if (isEq(node, format.block))
17065 				return TRUE;
17066 
17067 			// Check for selector match
17068 			if (format.selector)
17069 				return dom.is(node, format.selector);
17070 		};
17071 
17072 		function isEq(str1, str2) {
17073 			str1 = str1 || '';
17074 			str2 = str2 || '';
17075 
17076 			str1 = '' + (str1.nodeName || str1);
17077 			str2 = '' + (str2.nodeName || str2);
17078 
17079 			return str1.toLowerCase() == str2.toLowerCase();
17080 		};
17081 
17082 		function getStyle(node, name) {
17083 			var styleVal = dom.getStyle(node, name);
17084 
17085 			// Force the format to hex
17086 			if (name == 'color' || name == 'backgroundColor')
17087 				styleVal = dom.toHex(styleVal);
17088 
17089 			// Opera will return bold as 700
17090 			if (name == 'fontWeight' && styleVal == 700)
17091 				styleVal = 'bold';
17092 
17093 			return '' + styleVal;
17094 		};
17095 
17096 		function replaceVars(value, vars) {
17097 			if (typeof(value) != "string")
17098 				value = value(vars);
17099 			else if (vars) {
17100 				value = value.replace(/%(\w+)/g, function(str, name) {
17101 					return vars[name] || str;
17102 				});
17103 			}
17104 
17105 			return value;
17106 		};
17107 
17108 		function isWhiteSpaceNode(node) {
17109 			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
17110 		};
17111 
17112 		function wrap(node, name, attrs) {
17113 			var wrapper = dom.create(name, attrs);
17114 
17115 			node.parentNode.insertBefore(wrapper, node);
17116 			wrapper.appendChild(node);
17117 
17118 			return wrapper;
17119 		};
17120 
17121 		function expandRng(rng, format, remove) {
17122 			var sibling, lastIdx, leaf, endPoint,
17123 				startContainer = rng.startContainer,
17124 				startOffset = rng.startOffset,
17125 				endContainer = rng.endContainer,
17126 				endOffset = rng.endOffset;
17127 
17128 			// This function walks up the tree if there is no siblings before/after the node
17129 			function findParentContainer(start) {
17130 				var container, parent, child, sibling, siblingName, root;
17131 
17132 				container = parent = start ? startContainer : endContainer;
17133 				siblingName = start ? 'previousSibling' : 'nextSibling';
17134 				root = dom.getRoot();
17135 
17136 				// If it's a text node and the offset is inside the text
17137 				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
17138 					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
17139 						return container;
17140 					}
17141 				}
17142 
17143 				for (;;) {
17144 					// Stop expanding on block elements
17145 					if (!format[0].block_expand && isBlock(parent))
17146 						return parent;
17147 
17148 					// Walk left/right
17149 					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
17150 						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
17151 							return parent;
17152 						}
17153 					}
17154 
17155 					// Check if we can move up are we at root level or body level
17156 					if (parent.parentNode == root) {
17157 						container = parent;
17158 						break;
17159 					}
17160 
17161 					parent = parent.parentNode;
17162 				}
17163 
17164 				return container;
17165 			};
17166 
17167 			// This function walks down the tree to find the leaf at the selection.
17168 			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
17169 			function findLeaf(node, offset) {
17170 				if (offset === undef)
17171 					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17172 				while (node && node.hasChildNodes()) {
17173 					node = node.childNodes[offset];
17174 					if (node)
17175 						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17176 				}
17177 				return { node: node, offset: offset };
17178 			}
17179 
17180 			// If index based start position then resolve it
17181 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
17182 				lastIdx = startContainer.childNodes.length - 1;
17183 				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
17184 
17185 				if (startContainer.nodeType == 3)
17186 					startOffset = 0;
17187 			}
17188 
17189 			// If index based end position then resolve it
17190 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
17191 				lastIdx = endContainer.childNodes.length - 1;
17192 				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
17193 
17194 				if (endContainer.nodeType == 3)
17195 					endOffset = endContainer.nodeValue.length;
17196 			}
17197 
17198 			// Expands the node to the closes contentEditable false element if it exists
17199 			function findParentContentEditable(node) {
17200 				var parent = node;
17201 
17202 				while (parent) {
17203 					if (parent.nodeType === 1 && getContentEditable(parent)) {
17204 						return getContentEditable(parent) === "false" ? parent : node;
17205 					}
17206 
17207 					parent = parent.parentNode;
17208 				}
17209 
17210 				return node;
17211 			};
17212 
17213 			function findWordEndPoint(container, offset, start) {
17214 				var walker, node, pos, lastTextNode;
17215 
17216 				function findSpace(node, offset) {
17217 					var pos, pos2, str = node.nodeValue;
17218 
17219 					if (typeof(offset) == "undefined") {
17220 						offset = start ? str.length : 0;
17221 					}
17222 
17223 					if (start) {
17224 						pos = str.lastIndexOf(' ', offset);
17225 						pos2 = str.lastIndexOf('\u00a0', offset);
17226 						pos = pos > pos2 ? pos : pos2;
17227 
17228 						// Include the space on remove to avoid tag soup
17229 						if (pos !== -1 && !remove) {
17230 							pos++;
17231 						}
17232 					} else {
17233 						pos = str.indexOf(' ', offset);
17234 						pos2 = str.indexOf('\u00a0', offset);
17235 						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
17236 					}
17237 
17238 					return pos;
17239 				};
17240 
17241 				if (container.nodeType === 3) {
17242 					pos = findSpace(container, offset);
17243 
17244 					if (pos !== -1) {
17245 						return {container : container, offset : pos};
17246 					}
17247 
17248 					lastTextNode = container;
17249 				}
17250 
17251 				// Walk the nodes inside the block
17252 				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
17253 				while (node = walker[start ? 'prev' : 'next']()) {
17254 					if (node.nodeType === 3) {
17255 						lastTextNode = node;
17256 						pos = findSpace(node);
17257 
17258 						if (pos !== -1) {
17259 							return {container : node, offset : pos};
17260 						}
17261 					} else if (isBlock(node)) {
17262 						break;
17263 					}
17264 				}
17265 
17266 				if (lastTextNode) {
17267 					if (start) {
17268 						offset = 0;
17269 					} else {
17270 						offset = lastTextNode.length;
17271 					}
17272 
17273 					return {container: lastTextNode, offset: offset};
17274 				}
17275 			};
17276 
17277 			function findSelectorEndPoint(container, sibling_name) {
17278 				var parents, i, y, curFormat;
17279 
17280 				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
17281 					container = container[sibling_name];
17282 
17283 				parents = getParents(container);
17284 				for (i = 0; i < parents.length; i++) {
17285 					for (y = 0; y < format.length; y++) {
17286 						curFormat = format[y];
17287 
17288 						// If collapsed state is set then skip formats that doesn't match that
17289 						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
17290 							continue;
17291 
17292 						if (dom.is(parents[i], curFormat.selector))
17293 							return parents[i];
17294 					}
17295 				}
17296 
17297 				return container;
17298 			};
17299 
17300 			function findBlockEndPoint(container, sibling_name, sibling_name2) {
17301 				var node;
17302 
17303 				// Expand to block of similar type
17304 				if (!format[0].wrapper)
17305 					node = dom.getParent(container, format[0].block);
17306 
17307 				// Expand to first wrappable block element or any block element
17308 				if (!node)
17309 					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
17310 
17311 				// Exclude inner lists from wrapping
17312 				if (node && format[0].wrapper)
17313 					node = getParents(node, 'ul,ol').reverse()[0] || node;
17314 
17315 				// Didn't find a block element look for first/last wrappable element
17316 				if (!node) {
17317 					node = container;
17318 
17319 					while (node[sibling_name] && !isBlock(node[sibling_name])) {
17320 						node = node[sibling_name];
17321 
17322 						// Break on BR but include it will be removed later on
17323 						// we can't remove it now since we need to check if it can be wrapped
17324 						if (isEq(node, 'br'))
17325 							break;
17326 					}
17327 				}
17328 
17329 				return node || container;
17330 			};
17331 
17332 			// Expand to closest contentEditable element
17333 			startContainer = findParentContentEditable(startContainer);
17334 			endContainer = findParentContentEditable(endContainer);
17335 
17336 			// Exclude bookmark nodes if possible
17337 			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
17338 				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
17339 				startContainer = startContainer.nextSibling || startContainer;
17340 
17341 				if (startContainer.nodeType == 3)
17342 					startOffset = 0;
17343 			}
17344 
17345 			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
17346 				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
17347 				endContainer = endContainer.previousSibling || endContainer;
17348 
17349 				if (endContainer.nodeType == 3)
17350 					endOffset = endContainer.length;
17351 			}
17352 
17353 			if (format[0].inline) {
17354 				if (rng.collapsed) {
17355 					// Expand left to closest word boundery
17356 					endPoint = findWordEndPoint(startContainer, startOffset, true);
17357 					if (endPoint) {
17358 						startContainer = endPoint.container;
17359 						startOffset = endPoint.offset;
17360 					}
17361 
17362 					// Expand right to closest word boundery
17363 					endPoint = findWordEndPoint(endContainer, endOffset);
17364 					if (endPoint) {
17365 						endContainer = endPoint.container;
17366 						endOffset = endPoint.offset;
17367 					}
17368 				}
17369 
17370 				// Avoid applying formatting to a trailing space.
17371 				leaf = findLeaf(endContainer, endOffset);
17372 				if (leaf.node) {
17373 					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
17374 						leaf = findLeaf(leaf.node.previousSibling);
17375 
17376 					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
17377 							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
17378 
17379 						if (leaf.offset > 1) {
17380 							endContainer = leaf.node;
17381 							endContainer.splitText(leaf.offset - 1);
17382 						}
17383 					}
17384 				}
17385 			}
17386 
17387 			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
17388 			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
17389 			// This will reduce the number of wrapper elements that needs to be created
17390 			// Move start point up the tree
17391 			if (format[0].inline || format[0].block_expand) {
17392 				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
17393 					startContainer = findParentContainer(true);
17394 				}
17395 
17396 				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
17397 					endContainer = findParentContainer();
17398 				}
17399 			}
17400 
17401 			// Expand start/end container to matching selector
17402 			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
17403 				// Find new startContainer/endContainer if there is better one
17404 				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
17405 				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
17406 			}
17407 
17408 			// Expand start/end container to matching block element or text node
17409 			if (format[0].block || format[0].selector) {
17410 				// Find new startContainer/endContainer if there is better one
17411 				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
17412 				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
17413 
17414 				// Non block element then try to expand up the leaf
17415 				if (format[0].block) {
17416 					if (!isBlock(startContainer))
17417 						startContainer = findParentContainer(true);
17418 
17419 					if (!isBlock(endContainer))
17420 						endContainer = findParentContainer();
17421 				}
17422 			}
17423 
17424 			// Setup index for startContainer
17425 			if (startContainer.nodeType == 1) {
17426 				startOffset = nodeIndex(startContainer);
17427 				startContainer = startContainer.parentNode;
17428 			}
17429 
17430 			// Setup index for endContainer
17431 			if (endContainer.nodeType == 1) {
17432 				endOffset = nodeIndex(endContainer) + 1;
17433 				endContainer = endContainer.parentNode;
17434 			}
17435 
17436 			// Return new range like object
17437 			return {
17438 				startContainer : startContainer,
17439 				startOffset : startOffset,
17440 				endContainer : endContainer,
17441 				endOffset : endOffset
17442 			};
17443 		}
17444 
17445 		function removeFormat(format, vars, node, compare_node) {
17446 			var i, attrs, stylesModified;
17447 
17448 			// Check if node matches format
17449 			if (!matchName(node, format))
17450 				return FALSE;
17451 
17452 			// Should we compare with format attribs and styles
17453 			if (format.remove != 'all') {
17454 				// Remove styles
17455 				each(format.styles, function(value, name) {
17456 					value = replaceVars(value, vars);
17457 
17458 					// Indexed array
17459 					if (typeof(name) === 'number') {
17460 						name = value;
17461 						compare_node = 0;
17462 					}
17463 
17464 					if (!compare_node || isEq(getStyle(compare_node, name), value))
17465 						dom.setStyle(node, name, '');
17466 
17467 					stylesModified = 1;
17468 				});
17469 
17470 				// Remove style attribute if it's empty
17471 				if (stylesModified && dom.getAttrib(node, 'style') == '') {
17472 					node.removeAttribute('style');
17473 					node.removeAttribute('data-mce-style');
17474 				}
17475 
17476 				// Remove attributes
17477 				each(format.attributes, function(value, name) {
17478 					var valueOut;
17479 
17480 					value = replaceVars(value, vars);
17481 
17482 					// Indexed array
17483 					if (typeof(name) === 'number') {
17484 						name = value;
17485 						compare_node = 0;
17486 					}
17487 
17488 					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
17489 						// Keep internal classes
17490 						if (name == 'class') {
17491 							value = dom.getAttrib(node, name);
17492 							if (value) {
17493 								// Build new class value where everything is removed except the internal prefixed classes
17494 								valueOut = '';
17495 								each(value.split(/\s+/), function(cls) {
17496 									if (/mce\w+/.test(cls))
17497 										valueOut += (valueOut ? ' ' : '') + cls;
17498 								});
17499 
17500 								// We got some internal classes left
17501 								if (valueOut) {
17502 									dom.setAttrib(node, name, valueOut);
17503 									return;
17504 								}
17505 							}
17506 						}
17507 
17508 						// IE6 has a bug where the attribute doesn't get removed correctly
17509 						if (name == "class")
17510 							node.removeAttribute('className');
17511 
17512 						// Remove mce prefixed attributes
17513 						if (MCE_ATTR_RE.test(name))
17514 							node.removeAttribute('data-mce-' + name);
17515 
17516 						node.removeAttribute(name);
17517 					}
17518 				});
17519 
17520 				// Remove classes
17521 				each(format.classes, function(value) {
17522 					value = replaceVars(value, vars);
17523 
17524 					if (!compare_node || dom.hasClass(compare_node, value))
17525 						dom.removeClass(node, value);
17526 				});
17527 
17528 				// Check for non internal attributes
17529 				attrs = dom.getAttribs(node);
17530 				for (i = 0; i < attrs.length; i++) {
17531 					if (attrs[i].nodeName.indexOf('_') !== 0)
17532 						return FALSE;
17533 				}
17534 			}
17535 
17536 			// Remove the inline child if it's empty for example <b> or <span>
17537 			if (format.remove != 'none') {
17538 				removeNode(node, format);
17539 				return TRUE;
17540 			}
17541 		};
17542 
17543 		function removeNode(node, format) {
17544 			var parentNode = node.parentNode, rootBlockElm;
17545 
17546 			function find(node, next, inc) {
17547 				node = getNonWhiteSpaceSibling(node, next, inc);
17548 
17549 				return !node || (node.nodeName == 'BR' || isBlock(node));
17550 			};
17551 
17552 			if (format.block) {
17553 				if (!forcedRootBlock) {
17554 					// Append BR elements if needed before we remove the block
17555 					if (isBlock(node) && !isBlock(parentNode)) {
17556 						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
17557 							node.insertBefore(dom.create('br'), node.firstChild);
17558 
17559 						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
17560 							node.appendChild(dom.create('br'));
17561 					}
17562 				} else {
17563 					// Wrap the block in a forcedRootBlock if we are at the root of document
17564 					if (parentNode == dom.getRoot()) {
17565 						if (!format.list_block || !isEq(node, format.list_block)) {
17566 							each(tinymce.grep(node.childNodes), function(node) {
17567 								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
17568 									if (!rootBlockElm)
17569 										rootBlockElm = wrap(node, forcedRootBlock);
17570 									else
17571 										rootBlockElm.appendChild(node);
17572 								} else
17573 									rootBlockElm = 0;
17574 							});
17575 						}
17576 					}
17577 				}
17578 			}
17579 
17580 			// Never remove nodes that isn't the specified inline element if a selector is specified too
17581 			if (format.selector && format.inline && !isEq(format.inline, node))
17582 				return;
17583 
17584 			dom.remove(node, 1);
17585 		};
17586 
17587 		function getNonWhiteSpaceSibling(node, next, inc) {
17588 			if (node) {
17589 				next = next ? 'nextSibling' : 'previousSibling';
17590 
17591 				for (node = inc ? node : node[next]; node; node = node[next]) {
17592 					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
17593 						return node;
17594 				}
17595 			}
17596 		};
17597 
17598 		function isBookmarkNode(node) {
17599 			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
17600 		};
17601 
17602 		function mergeSiblings(prev, next) {
17603 			var marker, sibling, tmpSibling;
17604 
17605 			function compareElements(node1, node2) {
17606 				// Not the same name
17607 				if (node1.nodeName != node2.nodeName)
17608 					return FALSE;
17609 
17610 				function getAttribs(node) {
17611 					var attribs = {};
17612 
17613 					each(dom.getAttribs(node), function(attr) {
17614 						var name = attr.nodeName.toLowerCase();
17615 
17616 						// Don't compare internal attributes or style
17617 						if (name.indexOf('_') !== 0 && name !== 'style')
17618 							attribs[name] = dom.getAttrib(node, name);
17619 					});
17620 
17621 					return attribs;
17622 				};
17623 
17624 				function compareObjects(obj1, obj2) {
17625 					var value, name;
17626 
17627 					for (name in obj1) {
17628 						// Obj1 has item obj2 doesn't have
17629 						if (obj1.hasOwnProperty(name)) {
17630 							value = obj2[name];
17631 
17632 							// Obj2 doesn't have obj1 item
17633 							if (value === undef)
17634 								return FALSE;
17635 
17636 							// Obj2 item has a different value
17637 							if (obj1[name] != value)
17638 								return FALSE;
17639 
17640 							// Delete similar value
17641 							delete obj2[name];
17642 						}
17643 					}
17644 
17645 					// Check if obj 2 has something obj 1 doesn't have
17646 					for (name in obj2) {
17647 						// Obj2 has item obj1 doesn't have
17648 						if (obj2.hasOwnProperty(name))
17649 							return FALSE;
17650 					}
17651 
17652 					return TRUE;
17653 				};
17654 
17655 				// Attribs are not the same
17656 				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
17657 					return FALSE;
17658 
17659 				// Styles are not the same
17660 				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
17661 					return FALSE;
17662 
17663 				return TRUE;
17664 			};
17665 
17666 			function findElementSibling(node, sibling_name) {
17667 				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
17668 					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
17669 						return node;
17670 
17671 					if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
17672 						return sibling;
17673 				}
17674 
17675 				return node;
17676 			};
17677 
17678 			// Check if next/prev exists and that they are elements
17679 			if (prev && next) {
17680 				// If previous sibling is empty then jump over it
17681 				prev = findElementSibling(prev, 'previousSibling');
17682 				next = findElementSibling(next, 'nextSibling');
17683 
17684 				// Compare next and previous nodes
17685 				if (compareElements(prev, next)) {
17686 					// Append nodes between
17687 					for (sibling = prev.nextSibling; sibling && sibling != next;) {
17688 						tmpSibling = sibling;
17689 						sibling = sibling.nextSibling;
17690 						prev.appendChild(tmpSibling);
17691 					}
17692 
17693 					// Remove next node
17694 					dom.remove(next);
17695 
17696 					// Move children into prev node
17697 					each(tinymce.grep(next.childNodes), function(node) {
17698 						prev.appendChild(node);
17699 					});
17700 
17701 					return prev;
17702 				}
17703 			}
17704 
17705 			return next;
17706 		};
17707 
17708 		function isTextBlock(name) {
17709 			return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
17710 		};
17711 
17712 		function getContainer(rng, start) {
17713 			var container, offset, lastIdx, walker;
17714 
17715 			container = rng[start ? 'startContainer' : 'endContainer'];
17716 			offset = rng[start ? 'startOffset' : 'endOffset'];
17717 
17718 			if (container.nodeType == 1) {
17719 				lastIdx = container.childNodes.length - 1;
17720 
17721 				if (!start && offset)
17722 					offset--;
17723 
17724 				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
17725 			}
17726 
17727 			// If start text node is excluded then walk to the next node
17728 			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
17729 				container = new TreeWalker(container, ed.getBody()).next() || container;
17730 			}
17731 
17732 			// If end text node is excluded then walk to the previous node
17733 			if (container.nodeType === 3 && !start && offset === 0) {
17734 				container = new TreeWalker(container, ed.getBody()).prev() || container;
17735 			}
17736 
17737 			return container;
17738 		};
17739 
17740 		function performCaretAction(type, name, vars) {
17741 			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
17742 
17743 			// Creates a caret container bogus element
17744 			function createCaretContainer(fill) {
17745 				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
17746 
17747 				if (fill) {
17748 					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
17749 				}
17750 
17751 				return caretContainer;
17752 			};
17753 
17754 			function isCaretContainerEmpty(node, nodes) {
17755 				while (node) {
17756 					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
17757 						return false;
17758 					}
17759 
17760 					// Collect nodes
17761 					if (nodes && node.nodeType === 1) {
17762 						nodes.push(node);
17763 					}
17764 
17765 					node = node.firstChild;
17766 				}
17767 
17768 				return true;
17769 			};
17770 			
17771 			// Returns any parent caret container element
17772 			function getParentCaretContainer(node) {
17773 				while (node) {
17774 					if (node.id === caretContainerId) {
17775 						return node;
17776 					}
17777 
17778 					node = node.parentNode;
17779 				}
17780 			};
17781 
17782 			// Finds the first text node in the specified node
17783 			function findFirstTextNode(node) {
17784 				var walker;
17785 
17786 				if (node) {
17787 					walker = new TreeWalker(node, node);
17788 
17789 					for (node = walker.current(); node; node = walker.next()) {
17790 						if (node.nodeType === 3) {
17791 							return node;
17792 						}
17793 					}
17794 				}
17795 			};
17796 
17797 			// Removes the caret container for the specified node or all on the current document
17798 			function removeCaretContainer(node, move_caret) {
17799 				var child, rng;
17800 
17801 				if (!node) {
17802 					node = getParentCaretContainer(selection.getStart());
17803 
17804 					if (!node) {
17805 						while (node = dom.get(caretContainerId)) {
17806 							removeCaretContainer(node, false);
17807 						}
17808 					}
17809 				} else {
17810 					rng = selection.getRng(true);
17811 
17812 					if (isCaretContainerEmpty(node)) {
17813 						if (move_caret !== false) {
17814 							rng.setStartBefore(node);
17815 							rng.setEndBefore(node);
17816 						}
17817 
17818 						dom.remove(node);
17819 					} else {
17820 						child = findFirstTextNode(node);
17821 
17822 						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
17823 							child = child.deleteData(0, 1);
17824 						}
17825 
17826 						dom.remove(node, 1);
17827 					}
17828 
17829 					selection.setRng(rng);
17830 				}
17831 			};
17832 			
17833 			// Applies formatting to the caret postion
17834 			function applyCaretFormat() {
17835 				var rng, caretContainer, textNode, offset, bookmark, container, text;
17836 
17837 				rng = selection.getRng(true);
17838 				offset = rng.startOffset;
17839 				container = rng.startContainer;
17840 				text = container.nodeValue;
17841 
17842 				caretContainer = getParentCaretContainer(selection.getStart());
17843 				if (caretContainer) {
17844 					textNode = findFirstTextNode(caretContainer);
17845 				}
17846 
17847 				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
17848 				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
17849 					// Get bookmark of caret position
17850 					bookmark = selection.getBookmark();
17851 
17852 					// Collapse bookmark range (WebKit)
17853 					rng.collapse(true);
17854 
17855 					// Expand the range to the closest word and split it at those points
17856 					rng = expandRng(rng, get(name));
17857 					rng = rangeUtils.split(rng);
17858 
17859 					// Apply the format to the range
17860 					apply(name, vars, rng);
17861 
17862 					// Move selection back to caret position
17863 					selection.moveToBookmark(bookmark);
17864 				} else {
17865 					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
17866 						caretContainer = createCaretContainer(true);
17867 						textNode = caretContainer.firstChild;
17868 
17869 						rng.insertNode(caretContainer);
17870 						offset = 1;
17871 
17872 						apply(name, vars, caretContainer);
17873 					} else {
17874 						apply(name, vars, caretContainer);
17875 					}
17876 
17877 					// Move selection to text node
17878 					selection.setCursorLocation(textNode, offset);
17879 				}
17880 			};
17881 
17882 			function removeCaretFormat() {
17883 				var rng = selection.getRng(true), container, offset, bookmark,
17884 					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
17885 
17886 				container = rng.startContainer;
17887 				offset = rng.startOffset;
17888 				node = container;
17889 
17890 				if (container.nodeType == 3) {
17891 					if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
17892 						hasContentAfter = true;
17893 					}
17894 
17895 					node = node.parentNode;
17896 				}
17897 
17898 				while (node) {
17899 					if (matchNode(node, name, vars)) {
17900 						formatNode = node;
17901 						break;
17902 					}
17903 
17904 					if (node.nextSibling) {
17905 						hasContentAfter = true;
17906 					}
17907 
17908 					parents.push(node);
17909 					node = node.parentNode;
17910 				}
17911 
17912 				// Node doesn't have the specified format
17913 				if (!formatNode) {
17914 					return;
17915 				}
17916 
17917 				// Is there contents after the caret then remove the format on the element
17918 				if (hasContentAfter) {
17919 					// Get bookmark of caret position
17920 					bookmark = selection.getBookmark();
17921 
17922 					// Collapse bookmark range (WebKit)
17923 					rng.collapse(true);
17924 
17925 					// Expand the range to the closest word and split it at those points
17926 					rng = expandRng(rng, get(name), true);
17927 					rng = rangeUtils.split(rng);
17928 
17929 					// Remove the format from the range
17930 					remove(name, vars, rng);
17931 
17932 					// Move selection back to caret position
17933 					selection.moveToBookmark(bookmark);
17934 				} else {
17935 					caretContainer = createCaretContainer();
17936 
17937 					node = caretContainer;
17938 					for (i = parents.length - 1; i >= 0; i--) {
17939 						node.appendChild(dom.clone(parents[i], false));
17940 						node = node.firstChild;
17941 					}
17942 
17943 					// Insert invisible character into inner most format element
17944 					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
17945 					node = node.firstChild;
17946 
17947 					// Insert caret container after the formated node
17948 					dom.insertAfter(caretContainer, formatNode);
17949 
17950 					// Move selection to text node
17951 					selection.setCursorLocation(node, 1);
17952 				}
17953 			};
17954 
17955 			// Checks if the parent caret container node isn't empty if that is the case it
17956 			// will remove the bogus state on all children that isn't empty
17957 			function unmarkBogusCaretParents() {
17958 				var i, caretContainer, node;
17959 
17960 				caretContainer = getParentCaretContainer(selection.getStart());
17961 				if (caretContainer && !dom.isEmpty(caretContainer)) {
17962 					tinymce.walk(caretContainer, function(node) {
17963 						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
17964 							dom.setAttrib(node, 'data-mce-bogus', null);
17965 						}
17966 					}, 'childNodes');
17967 				}
17968 			};
17969 
17970 			// Only bind the caret events once
17971 			if (!self._hasCaretEvents) {
17972 				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
17973 				ed.onBeforeGetContent.addToTop(function() {
17974 					var nodes = [], i;
17975 
17976 					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
17977 						// Mark children
17978 						i = nodes.length;
17979 						while (i--) {
17980 							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
17981 						}
17982 					}
17983 				});
17984 
17985 				// Remove caret container on mouse up and on key up
17986 				tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
17987 					ed[name].addToTop(function() {
17988 						removeCaretContainer();
17989 						unmarkBogusCaretParents();
17990 					});
17991 				});
17992 
17993 				// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
17994 				ed.onKeyDown.addToTop(function(ed, e) {
17995 					var keyCode = e.keyCode;
17996 
17997 					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
17998 						removeCaretContainer(getParentCaretContainer(selection.getStart()));
17999 					}
18000 
18001 					unmarkBogusCaretParents();
18002 				});
18003 
18004 				// Remove bogus state if they got filled by contents using editor.selection.setContent
18005 				selection.onSetContent.add(unmarkBogusCaretParents);
18006 
18007 				self._hasCaretEvents = true;
18008 			}
18009 
18010 			// Do apply or remove caret format
18011 			if (type == "apply") {
18012 				applyCaretFormat();
18013 			} else {
18014 				removeCaretFormat();
18015 			}
18016 		};
18017 
18018 		function moveStart(rng) {
18019 			var container = rng.startContainer,
18020 					offset = rng.startOffset, isAtEndOfText,
18021 					walker, node, nodes, tmpNode;
18022 
18023 			// Convert text node into index if possible
18024 			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
18025 				// Get the parent container location and walk from there
18026 				offset = nodeIndex(container);
18027 				container = container.parentNode;
18028 				isAtEndOfText = true;
18029 			}
18030 
18031 			// Move startContainer/startOffset in to a suitable node
18032 			if (container.nodeType == 1) {
18033 				nodes = container.childNodes;
18034 				container = nodes[Math.min(offset, nodes.length - 1)];
18035 				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
18036 
18037 				// If offset is at end of the parent node walk to the next one
18038 				if (offset > nodes.length - 1 || isAtEndOfText)
18039 					walker.next();
18040 
18041 				for (node = walker.current(); node; node = walker.next()) {
18042 					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
18043 						// IE has a "neat" feature where it moves the start node into the closest element
18044 						// we can avoid this by inserting an element before it and then remove it after we set the selection
18045 						tmpNode = dom.create('a', null, INVISIBLE_CHAR);
18046 						node.parentNode.insertBefore(tmpNode, node);
18047 
18048 						// Set selection and remove tmpNode
18049 						rng.setStart(node, 0);
18050 						selection.setRng(rng);
18051 						dom.remove(tmpNode);
18052 
18053 						return;
18054 					}
18055 				}
18056 			}
18057 		};
18058 	};
18059 })(tinymce);
18060 
18061 tinymce.onAddEditor.add(function(tinymce, ed) {
18062 	var filters, fontSizes, dom, settings = ed.settings;
18063 
18064 	function replaceWithSpan(node, styles) {
18065 		tinymce.each(styles, function(value, name) {
18066 			if (value)
18067 				dom.setStyle(node, name, value);
18068 		});
18069 
18070 		dom.rename(node, 'span');
18071 	};
18072 
18073 	function convert(editor, params) {
18074 		dom = editor.dom;
18075 
18076 		if (settings.convert_fonts_to_spans) {
18077 			tinymce.each(dom.select('font,u,strike', params.node), function(node) {
18078 				filters[node.nodeName.toLowerCase()](ed.dom, node);
18079 			});
18080 		}
18081 	};
18082 
18083 	if (settings.inline_styles) {
18084 		fontSizes = tinymce.explode(settings.font_size_legacy_values);
18085 
18086 		filters = {
18087 			font : function(dom, node) {
18088 				replaceWithSpan(node, {
18089 					backgroundColor : node.style.backgroundColor,
18090 					color : node.color,
18091 					fontFamily : node.face,
18092 					fontSize : fontSizes[parseInt(node.size, 10) - 1]
18093 				});
18094 			},
18095 
18096 			u : function(dom, node) {
18097 				replaceWithSpan(node, {
18098 					textDecoration : 'underline'
18099 				});
18100 			},
18101 
18102 			strike : function(dom, node) {
18103 				replaceWithSpan(node, {
18104 					textDecoration : 'line-through'
18105 				});
18106 			}
18107 		};
18108 
18109 		ed.onPreProcess.add(convert);
18110 		ed.onSetContent.add(convert);
18111 
18112 		ed.onInit.add(function() {
18113 			ed.selection.onSetContent.add(convert);
18114 		});
18115 	}
18116 });
18117 
18118 (function(tinymce) {
18119 	var TreeWalker = tinymce.dom.TreeWalker;
18120 
18121 	tinymce.EnterKey = function(editor) {
18122 		var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
18123 
18124 		function handleEnterKey(evt) {
18125 			var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
18126 				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
18127 
18128 			// Returns true if the block can be split into two blocks or not
18129 			function canSplitBlock(node) {
18130 				return node &&
18131 					dom.isBlock(node) &&
18132 					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
18133 					!/^(fixed|absolute)/i.test(node.style.position) && 
18134 					dom.getContentEditable(node) !== "true";
18135 			};
18136 
18137 			// Renders empty block on IE
18138 			function renderBlockOnIE(block) {
18139 				var oldRng;
18140 
18141 				if (tinymce.isIE && dom.isBlock(block)) {
18142 					oldRng = selection.getRng();
18143 					block.appendChild(dom.create('span', null, '\u00a0'));
18144 					selection.select(block);
18145 					block.lastChild.outerHTML = '';
18146 					selection.setRng(oldRng);
18147 				}
18148 			};
18149 
18150 			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
18151 			function trimInlineElementsOnLeftSideOfBlock(block) {
18152 				var node = block, firstChilds = [], i;
18153 
18154 				// Find inner most first child ex: <p><i><b>*</b></i></p>
18155 				while (node = node.firstChild) {
18156 					if (dom.isBlock(node)) {
18157 						return;
18158 					}
18159 
18160 					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18161 						firstChilds.push(node);
18162 					}
18163 				}
18164 
18165 				i = firstChilds.length;
18166 				while (i--) {
18167 					node = firstChilds[i];
18168 					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
18169 						dom.remove(node);
18170 					}
18171 				}
18172 			};
18173 			
18174 			// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
18175 			function moveToCaretPosition(root) {
18176 				var walker, node, rng, y, viewPort, lastNode = root, tempElm;
18177 
18178 				rng = dom.createRng();
18179 
18180 				if (root.hasChildNodes()) {
18181 					walker = new TreeWalker(root, root);
18182 
18183 					while (node = walker.current()) {
18184 						if (node.nodeType == 3) {
18185 							rng.setStart(node, 0);
18186 							rng.setEnd(node, 0);
18187 							break;
18188 						}
18189 
18190 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18191 							rng.setStartBefore(node);
18192 							rng.setEndBefore(node);
18193 							break;
18194 						}
18195 
18196 						lastNode = node;
18197 						node = walker.next();
18198 					}
18199 
18200 					if (!node) {
18201 						rng.setStart(lastNode, 0);
18202 						rng.setEnd(lastNode, 0);
18203 					}
18204 				} else {
18205 					if (root.nodeName == 'BR') {
18206 						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
18207 							// Trick on older IE versions to render the caret before the BR between two lists
18208 							if (!documentMode || documentMode < 9) {
18209 								tempElm = dom.create('br');
18210 								root.parentNode.insertBefore(tempElm, root);
18211 							}
18212 
18213 							rng.setStartBefore(root);
18214 							rng.setEndBefore(root);
18215 						} else {
18216 							rng.setStartAfter(root);
18217 							rng.setEndAfter(root);
18218 						}
18219 					} else {
18220 						rng.setStart(root, 0);
18221 						rng.setEnd(root, 0);
18222 					}
18223 				}
18224 
18225 				selection.setRng(rng);
18226 
18227 				// Remove tempElm created for old IE:s
18228 				dom.remove(tempElm);
18229 
18230 				viewPort = dom.getViewPort(editor.getWin());
18231 
18232 				// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
18233 				y = dom.getPos(root).y;
18234 				if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
18235 					editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
18236 				}
18237 			};
18238 
18239 			// Creates a new block element by cloning the current one or creating a new one if the name is specified
18240 			// This function will also copy any text formatting from the parent block and add it to the new one
18241 			function createNewBlock(name) {
18242 				var node = container, block, clonedNode, caretNode;
18243 
18244 				block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
18245 				caretNode = block;
18246 
18247 				// Clone any parent styles
18248 				if (settings.keep_styles !== false) {
18249 					do {
18250 						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
18251 							clonedNode = node.cloneNode(false);
18252 							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
18253 
18254 							if (block.hasChildNodes()) {
18255 								clonedNode.appendChild(block.firstChild);
18256 								block.appendChild(clonedNode);
18257 							} else {
18258 								caretNode = clonedNode;
18259 								block.appendChild(clonedNode);
18260 							}
18261 						}
18262 					} while (node = node.parentNode);
18263 				}
18264 
18265 				// BR is needed in empty blocks on non IE browsers
18266 				if (!tinymce.isIE) {
18267 					caretNode.innerHTML = '<br>';
18268 				}
18269 
18270 				return block;
18271 			};
18272 
18273 			// Returns true/false if the caret is at the start/end of the parent block element
18274 			function isCaretAtStartOrEndOfBlock(start) {
18275 				var walker, node, name;
18276 
18277 				// Caret is in the middle of a text node like "a|b"
18278 				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
18279 					return false;
18280 				}
18281 
18282 				// If after the last element in block node edge case for #5091
18283 				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
18284 					return true;
18285 				}
18286 
18287 				// If the caret if before the first element in parentBlock
18288 				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
18289 					return true;
18290 				}
18291 
18292 				// Caret can be before/after a table
18293 				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
18294 					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
18295 				}
18296 
18297 				// Walk the DOM and look for text nodes or non empty elements
18298 				walker = new TreeWalker(container, parentBlock);
18299 	
18300 				// If caret is in beginning or end of a text block then jump to the next/previous node
18301 				if (container.nodeType == 3) {
18302 					if (start && offset == 0) {
18303 						walker.prev();
18304 					} else if (!start && offset == container.nodeValue.length) {
18305 						walker.next();
18306 					}
18307 				}
18308 
18309 				while (node = walker.current()) {
18310 					if (node.nodeType === 1) {
18311 						// Ignore bogus elements
18312 						if (!node.getAttribute('data-mce-bogus')) {
18313 							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
18314 							name = node.nodeName.toLowerCase();
18315 							if (nonEmptyElementsMap[name] && name !== 'br') {
18316 								return false;
18317 							}
18318 						}
18319 					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
18320 						return false;
18321 					}
18322 
18323 					if (start) {
18324 						walker.prev();
18325 					} else {
18326 						walker.next();
18327 					}
18328 				}
18329 
18330 				return true;
18331 			};
18332 
18333 			// Wraps any text nodes or inline elements in the specified forced root block name
18334 			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
18335 				var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
18336 
18337 				// Not in a block element or in a table cell or caption
18338 				parentBlock = dom.getParent(container, dom.isBlock);
18339 				if (!parentBlock || !canSplitBlock(parentBlock)) {
18340 					parentBlock = parentBlock || editableRoot;
18341 
18342 					if (!parentBlock.hasChildNodes()) {
18343 						newBlock = dom.create(blockName);
18344 						parentBlock.appendChild(newBlock);
18345 						rng.setStart(newBlock, 0);
18346 						rng.setEnd(newBlock, 0);
18347 						return newBlock;
18348 					}
18349 
18350 					// Find parent that is the first child of parentBlock
18351 					node = container;
18352 					while (node.parentNode != parentBlock) {
18353 						node = node.parentNode;
18354 					}
18355 
18356 					// Loop left to find start node start wrapping at
18357 					while (node && !dom.isBlock(node)) {
18358 						startNode = node;
18359 						node = node.previousSibling;
18360 					}
18361 
18362 					if (startNode) {
18363 						newBlock = dom.create(blockName);
18364 						startNode.parentNode.insertBefore(newBlock, startNode);
18365 
18366 						// Start wrapping until we hit a block
18367 						node = startNode;
18368 						while (node && !dom.isBlock(node)) {
18369 							next = node.nextSibling;
18370 							newBlock.appendChild(node);
18371 							node = next;
18372 						}
18373 
18374 						// Restore range to it's past location
18375 						rng.setStart(container, offset);
18376 						rng.setEnd(container, offset);
18377 					}
18378 				}
18379 
18380 				return container;
18381 			};
18382 
18383 			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
18384 			function handleEmptyListItem() {
18385 				function isFirstOrLastLi(first) {
18386 					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
18387 
18388 					// Find first/last element since there might be whitespace there
18389 					while (node) {
18390 						if (node.nodeType == 1) {
18391 							break;
18392 						}
18393 
18394 						node = node[first ? 'nextSibling' : 'previousSibling'];
18395 					}
18396 
18397 					return node === parentBlock;
18398 				};
18399 
18400 				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
18401 
18402 				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
18403 					// Is first and last list item then replace the OL/UL with a text block
18404 					dom.replace(newBlock, containerBlock);
18405 				} else if (isFirstOrLastLi(true)) {
18406 					// First LI in list then remove LI and add text block before list
18407 					containerBlock.parentNode.insertBefore(newBlock, containerBlock);
18408 				} else if (isFirstOrLastLi()) {
18409 					// Last LI in list then temove LI and add text block after list
18410 					dom.insertAfter(newBlock, containerBlock);
18411 					renderBlockOnIE(newBlock);
18412 				} else {
18413 					// Middle LI in list the split the list and insert a text block in the middle
18414 					// Extract after fragment and insert it after the current block
18415 					tmpRng = rng.cloneRange();
18416 					tmpRng.setStartAfter(parentBlock);
18417 					tmpRng.setEndAfter(containerBlock);
18418 					fragment = tmpRng.extractContents();
18419 					dom.insertAfter(fragment, containerBlock);
18420 					dom.insertAfter(newBlock, containerBlock);
18421 				}
18422 
18423 				dom.remove(parentBlock);
18424 				moveToCaretPosition(newBlock);
18425 				undoManager.add();
18426 			};
18427 
18428 			// Walks the parent block to the right and look for BR elements
18429 			function hasRightSideBr() {
18430 				var walker = new TreeWalker(container, parentBlock), node;
18431 
18432 				while (node = walker.current()) {
18433 					if (node.nodeName == 'BR') {
18434 						return true;
18435 					}
18436 
18437 					node = walker.next();
18438 				}
18439 			}
18440 			
18441 			// Inserts a BR element if the forced_root_block option is set to false or empty string
18442 			function insertBr() {
18443 				var brElm, extraBr;
18444 
18445 				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
18446 					// Insert extra BR element at the end block elements
18447 					if (!tinymce.isIE && !hasRightSideBr()) {
18448 						brElm = dom.create('br')
18449 						rng.insertNode(brElm);
18450 						rng.setStartAfter(brElm);
18451 						rng.setEndAfter(brElm);
18452 						extraBr = true;
18453 					}
18454 				}
18455 
18456 				brElm = dom.create('br');
18457 				rng.insertNode(brElm);
18458 
18459 				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
18460 				if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
18461 					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
18462 				}
18463 
18464 				if (!extraBr) {
18465 					rng.setStartAfter(brElm);
18466 					rng.setEndAfter(brElm);
18467 				} else {
18468 					rng.setStartBefore(brElm);
18469 					rng.setEndBefore(brElm);
18470 				}
18471 
18472 				selection.setRng(rng);
18473 				undoManager.add();
18474 			};
18475 
18476 			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
18477 			function trimLeadingLineBreaks(node) {
18478 				do {
18479 					if (node.nodeType === 3) {
18480 						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
18481 					}
18482 
18483 					node = node.firstChild;
18484 				} while (node);
18485 			};
18486 
18487 			function getEditableRoot(node) {
18488 				var root = dom.getRoot(), parent, editableRoot;
18489 
18490 				// Get all parents until we hit a non editable parent or the root
18491 				parent = node;
18492 				while (parent !== root && dom.getContentEditable(parent) !== "false") {
18493 					if (dom.getContentEditable(parent) === "true") {
18494 						editableRoot = parent;
18495 					}
18496 
18497 					parent = parent.parentNode;
18498 				}
18499 				
18500 				return parent !== root ? editableRoot : root;
18501 			};
18502 
18503 			// Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
18504 			function addBrToBlockIfNeeded(block) {
18505 				var lastChild;
18506 
18507 				// IE will render the blocks correctly other browsers needs a BR
18508 				if (!tinymce.isIE) {
18509 					block.normalize(); // Remove empty text nodes that got left behind by the extract
18510 
18511 					// Check if the block is empty or contains a floated last child
18512 					lastChild = block.lastChild;
18513 					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
18514 						dom.add(block, 'br');
18515 					}
18516 				}
18517 			};
18518 
18519 			// Delete any selected contents
18520 			if (!rng.collapsed) {
18521 				editor.execCommand('Delete');
18522 				return;
18523 			}
18524 
18525 			// Event is blocked by some other handler for example the lists plugin
18526 			if (evt.isDefaultPrevented()) {
18527 				return;
18528 			}
18529 
18530 			// Setup range items and newBlockName
18531 			container = rng.startContainer;
18532 			offset = rng.startOffset;
18533 			newBlockName = settings.forced_root_block;
18534 			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
18535 			documentMode = dom.doc.documentMode;
18536 
18537 			// Resolve node index
18538 			if (container.nodeType == 1 && container.hasChildNodes()) {
18539 				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
18540 				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
18541 				if (isAfterLastNodeInContainer && container.nodeType == 3) {
18542 					offset = container.nodeValue.length;
18543 				} else {
18544 					offset = 0;
18545 				}
18546 			}
18547 
18548 			// Get editable root node normaly the body element but sometimes a div or span
18549 			editableRoot = getEditableRoot(container);
18550 
18551 			// If there is no editable root then enter is done inside a contentEditable false element
18552 			if (!editableRoot) {
18553 				return;
18554 			}
18555 
18556 			undoManager.beforeChange();
18557 
18558 			// If editable root isn't block nor the root of the editor
18559 			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
18560 				if (!newBlockName || evt.shiftKey) {
18561 					insertBr();
18562 				}
18563 
18564 				return;
18565 			}
18566 
18567 			// Wrap the current node and it's sibling in a default block if it's needed.
18568 			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
18569 			// This won't happen if root blocks are disabled or the shiftKey is pressed
18570 			if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
18571 				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
18572 			}
18573 
18574 			// Find parent block and setup empty block paddings
18575 			parentBlock = dom.getParent(container, dom.isBlock);
18576 			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
18577 
18578 			// Setup block names
18579 			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18580 			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18581 
18582 			// Handle enter inside an empty list item
18583 			if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
18584 				// Let the list plugin or browser handle nested lists for now
18585 				if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
18586 					return false;
18587 				}
18588 
18589 				handleEmptyListItem();
18590 				return;
18591 			}
18592 
18593 			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
18594 			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
18595 				if (!evt.shiftKey) {
18596 					insertBr();
18597 					return;
18598 				}
18599 			} else {
18600 				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
18601 				if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
18602 					insertBr();
18603 					return;
18604 				}
18605 			}
18606 
18607 			// Default block name if it's not configured
18608 			newBlockName = newBlockName || 'P';
18609 
18610 			// Insert new block before/after the parent block depending on caret location
18611 			if (isCaretAtStartOrEndOfBlock()) {
18612 				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
18613 				if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
18614 					newBlock = createNewBlock(newBlockName);
18615 				} else {
18616 					newBlock = createNewBlock();
18617 				}
18618 
18619 				// Split the current container block element if enter is pressed inside an empty inner block element
18620 				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
18621 					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
18622 					newBlock = dom.split(containerBlock, parentBlock);
18623 				} else {
18624 					dom.insertAfter(newBlock, parentBlock);
18625 				}
18626 
18627 				moveToCaretPosition(newBlock);
18628 			} else if (isCaretAtStartOrEndOfBlock(true)) {
18629 				// Insert new block before
18630 				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
18631 				renderBlockOnIE(newBlock);
18632 			} else {
18633 				// Extract after fragment and insert it after the current block
18634 				tmpRng = rng.cloneRange();
18635 				tmpRng.setEndAfter(parentBlock);
18636 				fragment = tmpRng.extractContents();
18637 				trimLeadingLineBreaks(fragment);
18638 				newBlock = fragment.firstChild;
18639 				dom.insertAfter(fragment, parentBlock);
18640 				trimInlineElementsOnLeftSideOfBlock(newBlock);
18641 				addBrToBlockIfNeeded(parentBlock);
18642 				moveToCaretPosition(newBlock);
18643 			}
18644 
18645 			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
18646 			undoManager.add();
18647 		}
18648 
18649 		editor.onKeyDown.add(function(ed, evt) {
18650 			if (evt.keyCode == 13) {
18651 				if (handleEnterKey(evt) !== false) {
18652 					evt.preventDefault();
18653 				}
18654 			}
18655 		});
18656 	};
18657 })(tinymce);
18658 
18659